diff --git a/.circleci/config.yml b/.circleci/config.yml index 0f2637a48..b846264e8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,16 +6,22 @@ orbs: parameters: image_suffix: type: string - default: '-v5579d00' + default: '-v643b0b7' pg13_version: type: string - default: '13.4' + default: '13.8' pg14_version: type: string - default: '14.0' + default: '14.5' + pg15_version: + type: string + default: '15beta3' upgrade_pg_versions: type: string - default: '13.4-14.0' + default: '13.8-14.5-15beta3' + style_checker_tools_version: + type: string + default: '0.8.18' jobs: build: description: Build the citus extension @@ -46,7 +52,7 @@ jobs: check-style: docker: - - image: 'citus/stylechecker:latest' + - image: 'citus/stylechecker:<< pipeline.parameters.style_checker_tools_version >><< pipeline.parameters.image_suffix >>' steps: - checkout - run: @@ -111,7 +117,6 @@ jobs: image_tag: description: 'docker image tag to use' type: string - default: 12-13 docker: - image: '<< parameters.image >>:<< parameters.image_tag >><< pipeline.parameters.image_suffix >>' working_directory: /home/circleci/project @@ -192,7 +197,6 @@ jobs: image_tag: description: 'docker image tag to use' type: string - default: 12-13 docker: - image: '<< parameters.image >>:<< parameters.image_tag >><< pipeline.parameters.image_suffix >>' resource_class: xlarge @@ -409,6 +413,15 @@ jobs: - store_artifacts: name: 'Save core dumps' path: /tmp/core_dumps + - store_artifacts: + name: 'Save coordinator log' + path: src/test/regress/tmp_check/master/log + - store_artifacts: + name: 'Save worker1 log' + path: src/test/regress/tmp_check/worker.57637/log + - store_artifacts: + name: 'Save worker2 log' + path: src/test/regress/tmp_check/worker.57638/log - codecov/upload: flags: 'test_<< parameters.pg_major >>,<< parameters.make >>' when: always @@ -528,6 +541,10 @@ workflows: name: build-14 pg_major: 14 image_tag: '<< pipeline.parameters.pg14_version >>' + - build: + name: build-15 + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' - check-style - check-sql-snapshots @@ -767,6 +784,123 @@ workflows: make: check-failure requires: [build-14] + - test-citus: + name: 'test-15_check-split' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-split + requires: [build-15] + - test-citus: + name: 'test-15_check-enterprise' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-enterprise + requires: [build-15] + - test-citus: + name: 'test-15_check-enterprise-isolation' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-enterprise-isolation + requires: [build-15] + - test-citus: + name: 'test-15_check-enterprise-isolation-logicalrep-1' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-enterprise-isolation-logicalrep-1 + requires: [build-15] + - test-citus: + name: 'test-15_check-enterprise-isolation-logicalrep-2' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-enterprise-isolation-logicalrep-2 + requires: [build-15] + - test-citus: + name: 'test-15_check-enterprise-isolation-logicalrep-3' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-enterprise-isolation-logicalrep-3 + requires: [build-15] + - test-citus: + name: 'test-15_check-enterprise-failure' + pg_major: 15 + image: citus/failtester + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-enterprise-failure + requires: [build-15] + - test-citus: + name: 'test-15_check-multi' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-multi + requires: [build-15] + - test-citus: + name: 'test-15_check-multi-1' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-multi-1 + requires: [build-15] + - test-citus: + name: 'test-15_check-mx' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-multi-mx + requires: [build-15] + - test-citus: + name: 'test-15_check-vanilla' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-vanilla + requires: [build-15] + - test-citus: + name: 'test-15_check-isolation' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-isolation + requires: [build-15] + - test-citus: + name: 'test-15_check-operations' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-operations + requires: [build-15] + - test-citus: + name: 'test-15_check-follower-cluster' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-follower-cluster + requires: [build-15] + - test-citus: + name: 'test-15_check-columnar' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-columnar + requires: [build-15] + - test-citus: + name: 'test-15_check-columnar-isolation' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-columnar-isolation + requires: [build-15] + - tap-test-citus: + name: 'test-15_tap-recovery' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + suite: recovery + requires: [build-15] + - tap-test-citus: + name: 'test-15_tap-columnar-freezing' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + suite: columnar_freezing + requires: [build-15] + - test-citus: + name: 'test-15_check-failure' + pg_major: 15 + image: citus/failtester + image_tag: '<< pipeline.parameters.pg15_version >>' + make: check-failure + requires: [build-15] + - test-arbitrary-configs: name: 'test-13_check-arbitrary-configs' pg_major: 13 @@ -777,6 +911,11 @@ workflows: pg_major: 14 image_tag: '<< pipeline.parameters.pg14_version >>' requires: [build-14] + - test-arbitrary-configs: + name: 'test-15_check-arbitrary-configs' + pg_major: 15 + image_tag: '<< pipeline.parameters.pg15_version >>' + requires: [build-15] - test-pg-upgrade: name: 'test-13-14_check-pg-upgrade' @@ -785,6 +924,13 @@ workflows: image_tag: '<< pipeline.parameters.upgrade_pg_versions >>' requires: [build-13, build-14] + - test-pg-upgrade: + name: 'test-14-15_check-pg-upgrade' + old_pg_major: 14 + new_pg_major: 15 + image_tag: '<< pipeline.parameters.upgrade_pg_versions >>' + requires: [build-14, build-15] + - test-citus-upgrade: name: test-13_check-citus-upgrade pg_major: 13 diff --git a/.gitattributes b/.gitattributes index 0c508dff2..84765433b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,6 +27,7 @@ configure -whitespace src/backend/distributed/utils/citus_outfuncs.c -citus-style src/backend/distributed/deparser/ruleutils_13.c -citus-style src/backend/distributed/deparser/ruleutils_14.c -citus-style +src/backend/distributed/deparser/ruleutils_15.c -citus-style src/backend/distributed/commands/index_pg_source.c -citus-style src/include/distributed/citus_nodes.h -citus-style diff --git a/CHANGELOG.md b/CHANGELOG.md index 26f752cf2..e174e4eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +### citus v10.2.8 (August 19, 2022) ### + +* Fixes compilation warning caused by latest upgrade script changes + +* Fixes compilation warning on PG13 + OpenSSL 3.0 + +### citus v11.0.6 (August 19, 2022) ### + +* Fixes a bug that could cause failures in `CREATE ROLE` statement + +* Fixes a bug that could cause failures in `create_distributed_table` + +* Fixes a bug that prevents distributing tables that depend on sequences + +* Fixes reference table lock contention + +* Fixes upgrade paths for 11.0 + +### citus v10.2.7 (August 19, 2022) ### + +* Fixes a bug that could cause failures in `INSERT INTO .. SELECT` + +* Fixes a bug that could cause leaking files when materialized views are + refreshed + +* Fixes an unexpected error for foreign tables when upgrading Postgres + +* Fixes columnar freezing/wraparound bug + +* Fixes reference table lock contention + +* Prevents alter table functions from dropping extensions + ### citus v11.0.5 (August 1, 2022) ### * Avoids possible information leakage about existing users diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7253c92ed..6c54e9d6c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,17 @@ why we ask this as well as instructions for how to proceed, see the ### Getting and building +[PostgreSQL documentation](https://www.postgresql.org/support/versioning/) has a +section on upgrade policy. + + We always recommend that all users run the latest available minor release [for PostgreSQL] for whatever major version is in use. + +We expect Citus users to honor this recommendation and use latest available +PostgreSQL minor release. Failure to do so may result in failures in our test +suite. There are some known improvements in PG test architecture such as +[this commit](https://github.com/postgres/postgres/commit/3f323956128ff8589ce4d3a14e8b950837831803) +that are missing in earlier minor versions. + #### Mac 1. Install Xcode diff --git a/configure b/configure index 1188bca69..4c53cffb5 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'; then +elif test "$version_num" != '13' -a "$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 diff --git a/configure.ac b/configure.ac index a050c7407..7c1ab8c73 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'; then +elif test "$version_num" != '13' -a "$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/distributed/commands/alter_table.c b/src/backend/distributed/commands/alter_table.c index e31498fe4..59a9d8bbc 100644 --- a/src/backend/distributed/commands/alter_table.c +++ b/src/backend/distributed/commands/alter_table.c @@ -640,6 +640,8 @@ ConvertTable(TableConversionState *con) Oid partitionRelationId = InvalidOid; foreach_oid(partitionRelationId, partitionList) { + char *tableQualifiedName = generate_qualified_relation_name( + partitionRelationId); char *detachPartitionCommand = GenerateDetachPartitionCommand( partitionRelationId); char *attachPartitionCommand = GenerateAlterTableAttachPartitionCommand( @@ -685,6 +687,19 @@ ConvertTable(TableConversionState *con) foreignKeyCommands = list_concat(foreignKeyCommands, partitionReturn->foreignKeyCommands); } + + + /* + * If we are altering a partitioned distributed table by + * colocateWith:none, we override con->colocationWith parameter + * with the first newly created partition table to share the + * same colocation group for rest of partitions and partitioned + * table. + */ + if (con->colocateWith != NULL && IsColocateWithNone(con->colocateWith)) + { + con->colocateWith = tableQualifiedName; + } } } diff --git a/src/backend/distributed/commands/collation.c b/src/backend/distributed/commands/collation.c index 8904ab674..879dbeeba 100644 --- a/src/backend/distributed/commands/collation.c +++ b/src/backend/distributed/commands/collation.c @@ -63,15 +63,53 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati const char *collname = NameStr(collationForm->collname); bool collisdeterministic = collationForm->collisdeterministic; + char *collcollate; + char *collctype; + #if PG_VERSION_NUM >= PG_VERSION_15 + + /* + * In PG15, there is an added option to use ICU as global locale provider. + * pg_collation has three locale-related fields: collcollate and collctype, + * which are libc-related fields, and a new one colliculocale, which is the + * ICU-related field. Only the libc-related fields or the ICU-related field + * is set, never both. + */ + char *colliculocale; bool isnull; + Datum datum = SysCacheGetAttr(COLLOID, heapTuple, Anum_pg_collation_collcollate, &isnull); - Assert(!isnull); - char *collcollate = TextDatumGetCString(datum); + if (!isnull) + { + collcollate = TextDatumGetCString(datum); + } + else + { + collcollate = NULL; + } + datum = SysCacheGetAttr(COLLOID, heapTuple, Anum_pg_collation_collctype, &isnull); - Assert(!isnull); - char *collctype = TextDatumGetCString(datum); + if (!isnull) + { + collctype = TextDatumGetCString(datum); + } + else + { + collctype = NULL; + } + + datum = SysCacheGetAttr(COLLOID, heapTuple, Anum_pg_collation_colliculocale, &isnull); + if (!isnull) + { + colliculocale = TextDatumGetCString(datum); + } + else + { + colliculocale = NULL; + } + + AssertArg((collcollate && collctype) || colliculocale); #else /* @@ -79,8 +117,8 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati * pstrdup() to match the interface of 15 so that we consistently free the * result later. */ - char *collcollate = pstrdup(NameStr(collationForm->collcollate)); - char *collctype = pstrdup(NameStr(collationForm->collctype)); + collcollate = pstrdup(NameStr(collationForm->collcollate)); + collctype = pstrdup(NameStr(collationForm->collctype)); #endif if (collowner != NULL) @@ -106,6 +144,33 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati "CREATE COLLATION %s (provider = '%s'", *quotedCollationName, providerString); +#if PG_VERSION_NUM >= PG_VERSION_15 + if (colliculocale) + { + appendStringInfo(&collationNameDef, + ", locale = %s", + quote_literal_cstr(colliculocale)); + pfree(colliculocale); + } + else + { + if (strcmp(collcollate, collctype) == 0) + { + appendStringInfo(&collationNameDef, + ", locale = %s", + quote_literal_cstr(collcollate)); + } + else + { + appendStringInfo(&collationNameDef, + ", lc_collate = %s, lc_ctype = %s", + quote_literal_cstr(collcollate), + quote_literal_cstr(collctype)); + } + pfree(collcollate); + pfree(collctype); + } +#else if (strcmp(collcollate, collctype) == 0) { appendStringInfo(&collationNameDef, @@ -122,6 +187,7 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati pfree(collcollate); pfree(collctype); +#endif if (!collisdeterministic) { diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index 0ea1fa7ca..fd35acce2 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -42,6 +42,7 @@ #include "distributed/colocation_utils.h" #include "distributed/commands.h" #include "distributed/deparser.h" +#include "distributed/distributed_execution_locks.h" #include "distributed/distribution_column.h" #include "distributed/listutils.h" #include "distributed/local_executor.h" @@ -59,12 +60,16 @@ #include "distributed/reference_table_utils.h" #include "distributed/relation_access_tracking.h" #include "distributed/remote_commands.h" +#include "distributed/repair_shards.h" #include "distributed/resource_lock.h" +#include "distributed/shard_rebalancer.h" +#include "distributed/shard_split.h" #include "distributed/shared_library_init.h" #include "distributed/shard_rebalancer.h" #include "distributed/worker_protocol.h" #include "distributed/worker_shard_visibility.h" #include "distributed/worker_transaction.h" +#include "distributed/utils/distribution_column_map.h" #include "distributed/version_compat.h" #include "executor/executor.h" #include "executor/spi.h" @@ -76,6 +81,7 @@ #include "parser/parse_node.h" #include "parser/parse_relation.h" #include "parser/parser.h" +#include "postmaster/postmaster.h" #include "storage/lmgr.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -93,8 +99,18 @@ #define LOG_PER_TUPLE_AMOUNT 1000000 /* local function forward declarations */ +static void CreateDistributedTableConcurrently(Oid relationId, + char *distributionColumnName, + char distributionMethod, + char *colocateWithTableName, + int shardCount, + bool shardCountIsStrict); static char DecideReplicationModel(char distributionMethod, char *colocateWithTableName, bool viaDeprecatedAPI); +static List * HashSplitPointsForShardList(List *shardList); +static List * HashSplitPointsForShardCount(int shardCount); +static List * WorkerNodesForShardList(List *shardList); +static List * RoundRobinWorkerNodeList(List *workerNodeList, int listLength); static void CreateHashDistributedTableShards(Oid relationId, int shardCount, Oid colocatedTableId, bool localTableEmpty); static uint32 ColocationIdForNewTable(Oid relationId, Var *distributionColumn, @@ -105,9 +121,6 @@ static uint32 ColocationIdForNewTable(Oid relationId, Var *distributionColumn, static void EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, char distributionMethod, uint32 colocationId, char replicationModel, bool viaDeprecatedAPI); -static void EnsureTableCanBeColocatedWith(Oid relationId, char replicationModel, - Oid distributionColumnType, - Oid sourceRelationId); static void EnsureLocalTableEmpty(Oid relationId); static void EnsureRelationHasNoTriggers(Oid relationId); static Oid SupportFunctionForColumn(Var *partitionColumn, Oid accessMethodId, @@ -117,6 +130,7 @@ static void EnsureLocalTableEmptyIfNecessary(Oid relationId, char distributionMe static bool ShouldLocalTableBeEmpty(Oid relationId, char distributionMethod, bool viaDeprecatedAPI); static void EnsureCitusTableCanBeCreated(Oid relationOid); +static void PropagatePrerequisiteObjectsForDistributedTable(Oid relationId); static void EnsureDistributedSequencesHaveOneType(Oid relationId, List *seqInfoList); static List * GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId, @@ -134,9 +148,17 @@ static void DoCopyFromLocalTableIntoShards(Relation distributedRelation, EState *estate); static void ErrorIfTemporaryTable(Oid relationId); static void ErrorIfForeignTable(Oid relationOid); +static void SendAddLocalTableToMetadataCommandOutsideTransaction(Oid relationId); +static void EnsureDistributableTable(Oid relationId); +static void EnsureForeignKeysForDistributedTableConcurrently(Oid relationId); +static void EnsureColocateWithTableIsValid(Oid relationId, char distributionMethod, + char *distributionColumnName, + char *colocateWithTableName); +static void WarnIfTableHaveNoReplicaIdentity(Oid relationId); /* exports for SQL callable functions */ PG_FUNCTION_INFO_V1(master_create_distributed_table); +PG_FUNCTION_INFO_V1(create_distributed_table_concurrently); PG_FUNCTION_INFO_V1(create_distributed_table); PG_FUNCTION_INFO_V1(create_reference_table); @@ -254,6 +276,589 @@ create_distributed_table(PG_FUNCTION_ARGS) } +/* + * create_distributed_concurrently gets a table name, distribution column, + * distribution method and colocate_with option, then it creates a + * distributed table. + */ +Datum +create_distributed_table_concurrently(PG_FUNCTION_ARGS) +{ + CheckCitusVersion(ERROR); + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3)) + { + PG_RETURN_VOID(); + } + + Oid relationId = PG_GETARG_OID(0); + text *distributionColumnText = PG_GETARG_TEXT_P(1); + char *distributionColumnName = text_to_cstring(distributionColumnText); + Oid distributionMethodOid = PG_GETARG_OID(2); + char distributionMethod = LookupDistributionMethod(distributionMethodOid); + text *colocateWithTableNameText = PG_GETARG_TEXT_P(3); + char *colocateWithTableName = text_to_cstring(colocateWithTableNameText); + + bool shardCountIsStrict = false; + int shardCount = ShardCount; + if (!PG_ARGISNULL(4)) + { + if (pg_strncasecmp(colocateWithTableName, "default", NAMEDATALEN) != 0 && + pg_strncasecmp(colocateWithTableName, "none", NAMEDATALEN) != 0) + { + ereport(ERROR, (errmsg("Cannot use colocate_with with a table " + "and shard_count at the same time"))); + } + + shardCount = PG_GETARG_INT32(4); + + /* + * if shard_count parameter is given than we have to + * make sure table has that many shards + */ + shardCountIsStrict = true; + } + + CreateDistributedTableConcurrently(relationId, distributionColumnName, + distributionMethod, + colocateWithTableName, + shardCount, + shardCountIsStrict); + + PG_RETURN_VOID(); +} + + +/* + * CreateDistributedTableConcurrently distributes a table by first converting + * it to a Citus local table and then splitting the shard of the Citus local + * table. + * + * If anything goes wrong during the second phase, the table is left as a + * Citus local table. + */ +static void +CreateDistributedTableConcurrently(Oid relationId, char *distributionColumnName, + char distributionMethod, + char *colocateWithTableName, + int shardCount, + bool shardCountIsStrict) +{ + /* + * We disallow create_distributed_table_concurrently in transaction blocks + * because we cannot handle preceding writes, and we block writes at the + * very end of the operation so the transaction should end immediately after. + */ + PreventInTransactionBlock(true, "create_distributed_table_concurrently"); + + /* + * do not allow multiple create_distributed_table_concurrently in the same + * transaction. We should do that check just here because concurrent local table + * conversion can cause issues. + */ + ErrorIfMultipleNonblockingMoveSplitInTheSameTransaction(); + + /* do not allow concurrent CreateDistributedTableConcurrently operations */ + AcquireCreateDistributedTableConcurrentlyLock(relationId); + + if (distributionMethod != DISTRIBUTE_BY_HASH) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("only hash-distributed tables can be distributed " + "without blocking writes"))); + } + + if (ShardReplicationFactor > 1) + { + ereport(ERROR, (errmsg("cannot distribute a table concurrently when " + "citus.shard_replication_factor > 1"))); + } + + EnsureCoordinatorIsInMetadata(); + EnsureCitusTableCanBeCreated(relationId); + + EnsureValidDistributionColumn(relationId, distributionColumnName); + + /* + * Ensure table type is valid to be distributed. It should be either regular or citus local table. + */ + EnsureDistributableTable(relationId); + + /* + * we rely on citus_add_local_table_to_metadata, so it can generate irrelevant messages. + * we want to error with a user friendly message if foreign keys are not supported. + * We can miss foreign key violations because we are not holding locks, so relation + * can be modified until we acquire the lock for the relation, but we do as much as we can + * to be user friendly on foreign key violation messages. + */ + + EnsureForeignKeysForDistributedTableConcurrently(relationId); + + bool viaDeprecatedAPI = false; + char replicationModel = DecideReplicationModel(distributionMethod, + colocateWithTableName, + viaDeprecatedAPI); + + /* + * we fail transaction before local table conversion if the table could not be colocated with + * given table. We should make those checks after local table conversion by acquiring locks to + * the relation because the distribution column can be modified in that period. + */ + if (!IsColocateWithDefault(colocateWithTableName) && !IsColocateWithNone( + colocateWithTableName)) + { + EnsureColocateWithTableIsValid(relationId, distributionMethod, + distributionColumnName, + colocateWithTableName); + } + + /* + * Get name of the table before possibly replacing it in + * citus_add_local_table_to_metadata. + */ + char *tableName = get_rel_name(relationId); + Oid schemaId = get_rel_namespace(relationId); + char *schemaName = get_namespace_name(schemaId); + RangeVar *rangeVar = makeRangeVar(schemaName, tableName, -1); + + /* If table is a regular table, then we need to add it into metadata. */ + if (!IsCitusTable(relationId)) + { + /* + * Before taking locks, convert the table into a Citus local table and commit + * to allow shard split to see the shard. + */ + SendAddLocalTableToMetadataCommandOutsideTransaction(relationId); + } + + /* + * Lock target relation with a shard update exclusive lock to + * block DDL, but not writes. + * + * If there was a concurrent drop/rename, error out by setting missingOK = false. + */ + bool missingOK = false; + relationId = RangeVarGetRelid(rangeVar, ShareUpdateExclusiveLock, missingOK); + + if (PartitionedTableNoLock(relationId)) + { + /* also lock partitions */ + LockPartitionRelations(relationId, ShareUpdateExclusiveLock); + } + + WarnIfTableHaveNoReplicaIdentity(relationId); + + List *shardList = LoadShardIntervalList(relationId); + + /* + * It's technically possible for the table to have been concurrently + * distributed just after citus_add_local_table_to_metadata and just + * before acquiring the lock, so double check. + */ + if (list_length(shardList) != 1 || + !IsCitusTableType(relationId, CITUS_LOCAL_TABLE)) + { + ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("table was concurrently modified"))); + } + + /* + * The table currently has one shard, we will split that shard to match the + * target distribution. + */ + ShardInterval *shardToSplit = (ShardInterval *) linitial(shardList); + + PropagatePrerequisiteObjectsForDistributedTable(relationId); + + /* + * we should re-evaluate distribution column values. It may have changed, + * because we did not lock the relation at the previous check before local + * table conversion. + */ + Var *distributionColumn = BuildDistributionKeyFromColumnName(relationId, + distributionColumnName, + NoLock); + Oid distributionColumnType = distributionColumn->vartype; + Oid distributionColumnCollation = distributionColumn->varcollid; + + /* get an advisory lock to serialize concurrent default group creations */ + if (IsColocateWithDefault(colocateWithTableName)) + { + AcquireColocationDefaultLock(); + } + + /* + * At this stage, we only want to check for an existing co-location group. + * We cannot create a new co-location group until after replication slot + * creation in NonBlockingShardSplit. + */ + uint32 colocationId = FindColocateWithColocationId(relationId, + replicationModel, + distributionColumnType, + distributionColumnCollation, + shardCount, + shardCountIsStrict, + colocateWithTableName); + + if (IsColocateWithDefault(colocateWithTableName) && (colocationId != + INVALID_COLOCATION_ID)) + { + /* + * we can release advisory lock if there is already a default entry for given params; + * else, we should keep it to prevent different default coloc entry creation by + * concurrent operations. + */ + ReleaseColocationDefaultLock(); + } + + EnsureRelationCanBeDistributed(relationId, distributionColumn, distributionMethod, + colocationId, replicationModel, viaDeprecatedAPI); + + Oid colocatedTableId = InvalidOid; + if (colocationId != INVALID_COLOCATION_ID) + { + colocatedTableId = ColocatedTableId(colocationId); + } + + List *workersForPlacementList; + List *shardSplitPointsList; + + if (colocatedTableId != InvalidOid) + { + List *colocatedShardList = LoadShardIntervalList(colocatedTableId); + + /* + * Match the shard ranges of an existing table. + */ + shardSplitPointsList = HashSplitPointsForShardList(colocatedShardList); + + /* + * Find the node IDs of the shard placements. + */ + workersForPlacementList = WorkerNodesForShardList(colocatedShardList); + } + else + { + /* + * Generate a new set of #shardCount shards. + */ + shardSplitPointsList = HashSplitPointsForShardCount(shardCount); + + /* + * Place shards in a round-robin fashion across all data nodes. + */ + List *workerNodeList = DistributedTablePlacementNodeList(NoLock); + workersForPlacementList = RoundRobinWorkerNodeList(workerNodeList, shardCount); + } + + /* + * Make sure that existing reference tables have been replicated to all the nodes + * such that we can create foreign keys and joins work immediately after creation. + * We do this after applying all essential checks to error out early in case of + * user error. + */ + EnsureReferenceTablesExistOnAllNodes(); + + /* + * At this point, the table is a Citus local table, which means it does + * not have a partition column in the metadata. However, we cannot update + * the metadata here because that would prevent us from creating a replication + * slot to copy ongoing changes. Instead, we pass a hash that maps relation + * IDs to partition column vars. + */ + DistributionColumnMap *distributionColumnOverrides = CreateDistributionColumnMap(); + AddDistributionColumnForRelation(distributionColumnOverrides, relationId, + distributionColumnName); + + /* + * there is no colocation entries yet for local table, so we should + * check if table has any partition and add them to same colocation + * group + */ + List *sourceColocatedShardIntervalList = ListShardsUnderParentRelation(relationId); + + SplitMode splitMode = NON_BLOCKING_SPLIT; + SplitOperation splitOperation = CREATE_DISTRIBUTED_TABLE; + SplitShard( + splitMode, + splitOperation, + shardToSplit->shardId, + shardSplitPointsList, + workersForPlacementList, + distributionColumnOverrides, + sourceColocatedShardIntervalList, + colocationId + ); +} + + +/* + * EnsureForeignKeysForDistributedTableConcurrently ensures that referenced and referencing foreign + * keys for the given table are supported. + * + * We allow distributed -> reference + * distributed -> citus local + * + * We disallow reference -> distributed + * citus local -> distributed + * regular -> distributed + * + * Normally regular -> distributed is allowed but it is not allowed when we create the + * distributed table concurrently because we rely on conversion of regular table to citus local table, + * which errors with an unfriendly message. + */ +static void +EnsureForeignKeysForDistributedTableConcurrently(Oid relationId) +{ + /* + * disallow citus local -> distributed fkeys. + * disallow reference -> distributed fkeys. + * disallow regular -> distributed fkeys. + */ + EnsureNoFKeyFromTableType(relationId, INCLUDE_CITUS_LOCAL_TABLES | + INCLUDE_REFERENCE_TABLES | INCLUDE_LOCAL_TABLES); + + /* + * disallow distributed -> regular fkeys. + */ + EnsureNoFKeyToTableType(relationId, INCLUDE_LOCAL_TABLES); +} + + +/* + * EnsureColocateWithTableIsValid ensures given relation can be colocated with the table of given name. + */ +static void +EnsureColocateWithTableIsValid(Oid relationId, char distributionMethod, + char *distributionColumnName, char *colocateWithTableName) +{ + bool viaDeprecatedAPI = false; + char replicationModel = DecideReplicationModel(distributionMethod, + colocateWithTableName, + viaDeprecatedAPI); + + /* + * we fail transaction before local table conversion if the table could not be colocated with + * given table. We should make those checks after local table conversion by acquiring locks to + * the relation because the distribution column can be modified in that period. + */ + Oid distributionColumnType = ColumnTypeIdForRelationColumnName(relationId, + distributionColumnName); + + text *colocateWithTableNameText = cstring_to_text(colocateWithTableName); + Oid colocateWithTableId = ResolveRelationId(colocateWithTableNameText, false); + EnsureTableCanBeColocatedWith(relationId, replicationModel, + distributionColumnType, colocateWithTableId); +} + + +/* + * AcquireCreateDistributedTableConcurrentlyLock does not allow concurrent create_distributed_table_concurrently + * operations. + */ +void +AcquireCreateDistributedTableConcurrentlyLock(Oid relationId) +{ + LOCKTAG tag; + const bool sessionLock = false; + const bool dontWait = true; + + SET_LOCKTAG_CITUS_OPERATION(tag, CITUS_CREATE_DISTRIBUTED_TABLE_CONCURRENTLY); + + LockAcquireResult lockAcquired = LockAcquire(&tag, ExclusiveLock, sessionLock, + dontWait); + if (!lockAcquired) + { + ereport(ERROR, (errmsg("another create_distributed_table_concurrently " + "operation is in progress"), + errhint("Make sure that the concurrent operation has " + "finished and re-run the command"))); + } +} + + +/* + * SendAddLocalTableToMetadataCommandOutsideTransaction executes metadata add local + * table command locally to avoid deadlock. + */ +static void +SendAddLocalTableToMetadataCommandOutsideTransaction(Oid relationId) +{ + char *qualifiedRelationName = generate_qualified_relation_name(relationId); + + /* + * we need to allow nested distributed execution, because we start a new distributed + * execution inside the pushed-down UDF citus_add_local_table_to_metadata. Normally + * citus does not allow that because it cannot guarantee correctness. + */ + StringInfo allowNestedDistributionCommand = makeStringInfo(); + appendStringInfo(allowNestedDistributionCommand, + "SET LOCAL citus.allow_nested_distributed_execution to ON"); + + StringInfo addLocalTableToMetadataCommand = makeStringInfo(); + appendStringInfo(addLocalTableToMetadataCommand, + "SELECT pg_catalog.citus_add_local_table_to_metadata(%s)", + quote_literal_cstr(qualifiedRelationName)); + + List *commands = list_make2(allowNestedDistributionCommand->data, + addLocalTableToMetadataCommand->data); + char *username = NULL; + SendCommandListToWorkerOutsideTransaction(LocalHostName, PostPortNumber, username, + commands); +} + + +/* + * WarnIfTableHaveNoReplicaIdentity notices user if the given table or its partitions (if any) + * do not have a replica identity which is required for logical replication to replicate + * UPDATE and DELETE commands during create_distributed_table_concurrently. + */ +void +WarnIfTableHaveNoReplicaIdentity(Oid relationId) +{ + bool foundRelationWithNoReplicaIdentity = false; + + /* + * Check for source relation's partitions if any. We do not need to check for the source relation + * because we can replicate partitioned table even if it does not have replica identity. + * Source table will have no data if it has partitions. + */ + if (PartitionedTable(relationId)) + { + List *partitionList = PartitionList(relationId); + ListCell *partitionCell = NULL; + + foreach(partitionCell, partitionList) + { + Oid partitionTableId = lfirst_oid(partitionCell); + + if (!RelationCanPublishAllModifications(partitionTableId)) + { + foundRelationWithNoReplicaIdentity = true; + break; + } + } + } + /* check for source relation if it is not partitioned */ + else + { + if (!RelationCanPublishAllModifications(relationId)) + { + foundRelationWithNoReplicaIdentity = true; + } + } + + if (foundRelationWithNoReplicaIdentity) + { + char *relationName = get_rel_name(relationId); + + ereport(NOTICE, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("relation %s does not have a REPLICA " + "IDENTITY or PRIMARY KEY", relationName), + errdetail("UPDATE and DELETE commands on the relation will " + "error out during create_distributed_table_concurrently unless " + "there is a REPLICA IDENTITY or PRIMARY KEY. " + "INSERT commands will still work."))); + } +} + + +/* + * HashSplitPointsForShardList returns a list of split points which match + * the shard ranges of the given list of shards; + */ +static List * +HashSplitPointsForShardList(List *shardList) +{ + List *splitPointList = NIL; + + ShardInterval *shardInterval = NULL; + foreach_ptr(shardInterval, shardList) + { + int32 shardMaxValue = DatumGetInt32(shardInterval->maxValue); + + splitPointList = lappend_int(splitPointList, shardMaxValue); + } + + /* + * Split point lists only include the upper boundaries. + */ + splitPointList = list_delete_last(splitPointList); + + return splitPointList; +} + + +/* + * HashSplitPointsForShardCount returns a list of split points for a given + * shard count with roughly equal hash ranges. + */ +static List * +HashSplitPointsForShardCount(int shardCount) +{ + List *splitPointList = NIL; + + /* calculate the split of the hash space */ + uint64 hashTokenIncrement = HASH_TOKEN_COUNT / shardCount; + + /* + * Split points lists only include the upper boundaries, so we only + * go up to shardCount - 1 and do not have to apply the correction + * for the last shardmaxvalue. + */ + for (int64 shardIndex = 0; shardIndex < shardCount - 1; shardIndex++) + { + /* initialize the hash token space for this shard */ + int32 shardMinValue = PG_INT32_MIN + (shardIndex * hashTokenIncrement); + int32 shardMaxValue = shardMinValue + (hashTokenIncrement - 1); + + splitPointList = lappend_int(splitPointList, shardMaxValue); + } + + return splitPointList; +} + + +/* + * WorkerNodesForShardList returns a list of node ids reflecting the locations of + * the given list of shards. + */ +static List * +WorkerNodesForShardList(List *shardList) +{ + List *nodeIdList = NIL; + + ShardInterval *shardInterval = NULL; + foreach_ptr(shardInterval, shardList) + { + WorkerNode *workerNode = ActiveShardPlacementWorkerNode(shardInterval->shardId); + nodeIdList = lappend_int(nodeIdList, workerNode->nodeId); + } + + return nodeIdList; +} + + +/* + * RoundRobinWorkerNodeList round robins over the workers in the worker node list + * and adds node ids to a list of length listLength. + */ +static List * +RoundRobinWorkerNodeList(List *workerNodeList, int listLength) +{ + List *nodeIdList = NIL; + + for (int idx = 0; idx < listLength; idx++) + { + int nodeIdx = idx % list_length(workerNodeList); + WorkerNode *workerNode = (WorkerNode *) list_nth(workerNodeList, nodeIdx); + nodeIdList = lappend_int(nodeIdList, workerNode->nodeId); + } + + return nodeIdList; +} + + /* * create_reference_table creates a distributed table with the given relationId. The * created table has one shard and replication factor is set to the active worker @@ -394,7 +999,6 @@ CreateDistributedTable(Oid relationId, char *distributionColumnName, INCLUDE_ALL_TABLE_TYPES); relationId = DropFKeysAndUndistributeTable(relationId); } - /* * To support foreign keys between reference tables and local tables, * we drop & re-define foreign keys at the end of this function so @@ -431,21 +1035,9 @@ CreateDistributedTable(Oid relationId, char *distributionColumnName, LockRelationOid(relationId, ExclusiveLock); - /* - * Ensure that the sequences used in column defaults of the table - * have proper types - */ - EnsureRelationHasCompatibleSequenceTypes(relationId); + EnsureTableNotDistributed(relationId); - /* - * distributed tables might have dependencies on different objects, since we create - * shards for a distributed table via multiple sessions these objects will be created - * via their own connection and committed immediately so they become visible to all - * sessions creating shards. - */ - ObjectAddress *tableAddress = palloc0(sizeof(ObjectAddress)); - ObjectAddressSet(*tableAddress, RelationRelationId, relationId); - EnsureAllObjectDependenciesExistOnAllNodes(list_make1(tableAddress)); + PropagatePrerequisiteObjectsForDistributedTable(relationId); char replicationModel = DecideReplicationModel(distributionMethod, colocateWithTableName, @@ -453,7 +1045,7 @@ CreateDistributedTable(Oid relationId, char *distributionColumnName, Var *distributionColumn = BuildDistributionKeyFromColumnName(relationId, distributionColumnName, - ExclusiveLock); + NoLock); /* * ColocationIdForNewTable assumes caller acquires lock on relationId. In our case, @@ -582,6 +1174,31 @@ CreateDistributedTable(Oid relationId, char *distributionColumnName, } +/* + * PropagatePrerequisiteObjectsForDistributedTable ensures we can create shards + * on all nodes by ensuring all dependent objects exist on all node. + */ +static void +PropagatePrerequisiteObjectsForDistributedTable(Oid relationId) +{ + /* + * Ensure that the sequences used in column defaults of the table + * have proper types + */ + EnsureRelationHasCompatibleSequenceTypes(relationId); + + /* + * distributed tables might have dependencies on different objects, since we create + * shards for a distributed table via multiple sessions these objects will be created + * via their own connection and committed immediately so they become visible to all + * sessions creating shards. + */ + ObjectAddress *tableAddress = palloc0(sizeof(ObjectAddress)); + ObjectAddressSet(*tableAddress, RelationRelationId, relationId); + EnsureAllObjectDependenciesExistOnAllNodes(list_make1(tableAddress)); +} + + /* * EnsureSequenceTypeSupported ensures that the type of the column that uses * a sequence on its DEFAULT is consistent with previous uses (if any) of the @@ -956,80 +1573,56 @@ ColocationIdForNewTable(Oid relationId, Var *distributionColumn, */ Assert(distributionMethod == DISTRIBUTE_BY_HASH); - Relation pgDistColocation = table_open(DistColocationRelationId(), ExclusiveLock); - Oid distributionColumnType = distributionColumn->vartype; Oid distributionColumnCollation = get_typcollation(distributionColumnType); - bool createdColocationGroup = false; - if (pg_strncasecmp(colocateWithTableName, "default", NAMEDATALEN) == 0) + /* get an advisory lock to serialize concurrent default group creations */ + if (IsColocateWithDefault(colocateWithTableName)) { - /* check for default colocation group */ - colocationId = ColocationId(shardCount, ShardReplicationFactor, - distributionColumnType, - distributionColumnCollation); + AcquireColocationDefaultLock(); + } + colocationId = FindColocateWithColocationId(relationId, + replicationModel, + distributionColumnType, + distributionColumnCollation, + shardCount, + shardCountIsStrict, + colocateWithTableName); + + if (IsColocateWithDefault(colocateWithTableName) && (colocationId != + INVALID_COLOCATION_ID)) + { /* - * if the shardCount is strict then we check if the shard count - * of the colocated table is actually shardCount + * we can release advisory lock if there is already a default entry for given params; + * else, we should keep it to prevent different default coloc entry creation by + * concurrent operations. */ - if (shardCountIsStrict && colocationId != INVALID_COLOCATION_ID) - { - Oid colocatedTableId = ColocatedTableId(colocationId); - - if (colocatedTableId != InvalidOid) - { - CitusTableCacheEntry *cacheEntry = - GetCitusTableCacheEntry(colocatedTableId); - int colocatedTableShardCount = cacheEntry->shardIntervalArrayLength; - - if (colocatedTableShardCount != shardCount) - { - colocationId = INVALID_COLOCATION_ID; - } - } - } - - if (colocationId == INVALID_COLOCATION_ID) + ReleaseColocationDefaultLock(); + } + + if (colocationId == INVALID_COLOCATION_ID) + { + if (IsColocateWithDefault(colocateWithTableName)) { + /* + * Generate a new colocation ID and insert a pg_dist_colocation + * record. + */ + colocationId = CreateColocationGroup(shardCount, ShardReplicationFactor, + distributionColumnType, + distributionColumnCollation); + } + else if (IsColocateWithNone(colocateWithTableName)) + { + /* + * Generate a new colocation ID and insert a pg_dist_colocation + * record. + */ colocationId = CreateColocationGroup(shardCount, ShardReplicationFactor, distributionColumnType, distributionColumnCollation); - createdColocationGroup = true; } - } - else if (IsColocateWithNone(colocateWithTableName)) - { - colocationId = GetNextColocationId(); - - createdColocationGroup = true; - } - else - { - text *colocateWithTableNameText = cstring_to_text(colocateWithTableName); - Oid sourceRelationId = ResolveRelationId(colocateWithTableNameText, false); - - EnsureTableCanBeColocatedWith(relationId, replicationModel, - distributionColumnType, sourceRelationId); - - colocationId = TableColocationId(sourceRelationId); - } - - /* - * If we created a new colocation group then we need to keep the lock to - * prevent a concurrent create_distributed_table call from creating another - * colocation group with the same parameters. If we're using an existing - * colocation group then other transactions will use the same one. - */ - if (createdColocationGroup) - { - /* keep the exclusive lock */ - table_close(pgDistColocation, NoLock); - } - else - { - /* release the exclusive lock */ - table_close(pgDistColocation, ExclusiveLock); } } @@ -1053,7 +1646,6 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, { Oid parentRelationId = InvalidOid; - EnsureTableNotDistributed(relationId); EnsureLocalTableEmptyIfNecessary(relationId, distributionMethod, viaDeprecatedAPI); /* user really wants triggers? */ @@ -1129,13 +1721,13 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, } } - if (PartitionTable(relationId)) + if (PartitionTableNoLock(relationId)) { parentRelationId = PartitionParentOid(relationId); } /* partitions cannot be distributed if their parent is not distributed */ - if (PartitionTable(relationId) && !IsCitusTable(parentRelationId)) + if (PartitionTableNoLock(relationId) && !IsCitusTable(parentRelationId)) { char *parentRelationName = get_rel_name(parentRelationId); @@ -1153,7 +1745,7 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, * reach this point because, we call CreateDistributedTable for partitions if their * parent table is distributed. */ - if (PartitionedTable(relationId)) + if (PartitionedTableNoLock(relationId)) { /* we cannot distribute partitioned tables with master_create_distributed_table */ if (viaDeprecatedAPI) @@ -1172,7 +1764,7 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, } /* we don't support distributing tables with multi-level partitioning */ - if (PartitionTable(relationId)) + if (PartitionTableNoLock(relationId)) { char *parentRelationName = get_rel_name(parentRelationId); @@ -1225,55 +1817,6 @@ ErrorIfTableIsACatalogTable(Relation relation) } -/* - * EnsureTableCanBeColocatedWith checks whether a given replication model and - * distribution column type is suitable to distribute a table to be colocated - * with given source table. - * - * We only pass relationId to provide meaningful error messages. - */ -static void -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)) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot distribute relation"), - errdetail("Currently, colocate_with option is only supported " - "for hash distributed tables."))); - } - - if (sourceReplicationModel != replicationModel) - { - char *relationName = get_rel_name(relationId); - char *sourceRelationName = get_rel_name(sourceRelationId); - - ereport(ERROR, (errmsg("cannot colocate tables %s and %s", - sourceRelationName, relationName), - errdetail("Replication models don't match for %s and %s.", - sourceRelationName, relationName))); - } - - Oid sourceDistributionColumnType = sourceDistributionColumn->vartype; - if (sourceDistributionColumnType != distributionColumnType) - { - char *relationName = get_rel_name(relationId); - char *sourceRelationName = get_rel_name(sourceRelationId); - - ereport(ERROR, (errmsg("cannot colocate tables %s and %s", - sourceRelationName, relationName), - errdetail("Distribution column types don't match for " - "%s and %s.", sourceRelationName, - relationName))); - } -} - - /* * EnsureLocalTableEmptyIfNecessary errors out if the function should be empty * according to ShouldLocalTableBeEmpty but it is not. @@ -1348,6 +1891,27 @@ EnsureLocalTableEmpty(Oid relationId) } +/* + * EnsureDistributableTable ensures the given table type is appropriate to + * be distributed. Table type should be regular or citus local table. + */ +static void +EnsureDistributableTable(Oid relationId) +{ + bool isLocalTable = IsCitusTableType(relationId, CITUS_LOCAL_TABLE); + bool isRegularTable = !IsCitusTableType(relationId, ANY_CITUS_TABLE_TYPE); + + if (!isLocalTable && !isRegularTable) + { + char *relationName = get_rel_name(relationId); + + ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("table \"%s\" is already distributed", + relationName))); + } +} + + /* * EnsureTableNotDistributed errors out if the table is distributed. */ diff --git a/src/backend/distributed/commands/drop_distributed_table.c b/src/backend/distributed/commands/drop_distributed_table.c index d4718aab8..1e214adf3 100644 --- a/src/backend/distributed/commands/drop_distributed_table.c +++ b/src/backend/distributed/commands/drop_distributed_table.c @@ -12,6 +12,7 @@ #include "miscadmin.h" +#include "distributed/colocation_utils.h" #include "distributed/commands/utility_hook.h" #include "distributed/commands.h" #include "distributed/metadata_utility.h" @@ -70,6 +71,8 @@ master_remove_partition_metadata(PG_FUNCTION_ARGS) char *schemaName = text_to_cstring(schemaNameText); char *tableName = text_to_cstring(tableNameText); + uint32 colocationId = ColocationIdViaCatalog(relationId); + /* * The SQL_DROP trigger calls this function even for tables that are * not distributed. In that case, silently ignore. This is not very @@ -87,6 +90,8 @@ master_remove_partition_metadata(PG_FUNCTION_ARGS) DeletePartitionRow(relationId); + DeleteColocationGroupIfNoTablesBelong(colocationId); + PG_RETURN_VOID(); } diff --git a/src/backend/distributed/commands/foreign_constraint.c b/src/backend/distributed/commands/foreign_constraint.c index a84e8cce2..1cf6a0956 100644 --- a/src/backend/distributed/commands/foreign_constraint.c +++ b/src/backend/distributed/commands/foreign_constraint.c @@ -97,6 +97,66 @@ ConstraintIsAForeignKeyToReferenceTable(char *inputConstaintName, Oid relationId } +/* + * EnsureNoFKeyFromTableType ensures that given relation is not referenced by any table specified + * by table type flag. + */ +void +EnsureNoFKeyFromTableType(Oid relationId, int tableTypeFlag) +{ + int flags = INCLUDE_REFERENCED_CONSTRAINTS | EXCLUDE_SELF_REFERENCES | + tableTypeFlag; + List *referencedFKeyOids = GetForeignKeyOids(relationId, flags); + + if (list_length(referencedFKeyOids) > 0) + { + Oid referencingFKeyOid = linitial_oid(referencedFKeyOids); + Oid referencingTableId = GetReferencingTableId(referencingFKeyOid); + + char *referencingRelName = get_rel_name(referencingTableId); + char *referencedRelName = get_rel_name(relationId); + char *referencingTableTypeName = GetTableTypeName(referencingTableId); + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("relation %s is referenced by a foreign key from %s", + referencedRelName, referencingRelName), + errdetail( + "foreign keys from a %s to a distributed table are not supported.", + referencingTableTypeName))); + } +} + + +/* + * EnsureNoFKeyToTableType ensures that given relation is not referencing by any table specified + * by table type flag. + */ +void +EnsureNoFKeyToTableType(Oid relationId, int tableTypeFlag) +{ + int flags = INCLUDE_REFERENCING_CONSTRAINTS | EXCLUDE_SELF_REFERENCES | + tableTypeFlag; + List *referencingFKeyOids = GetForeignKeyOids(relationId, flags); + + if (list_length(referencingFKeyOids) > 0) + { + Oid referencedFKeyOid = linitial_oid(referencingFKeyOids); + Oid referencedTableId = GetReferencedTableId(referencedFKeyOid); + + char *referencedRelName = get_rel_name(referencedTableId); + char *referencingRelName = get_rel_name(relationId); + char *referencedTableTypeName = GetTableTypeName(referencedTableId); + + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("relation %s is referenced by a foreign key from %s", + referencedRelName, referencingRelName), + errdetail( + "foreign keys from a distributed table to a %s are not supported.", + referencedTableTypeName))); + } +} + + /* * ErrorIfUnsupportedForeignConstraintExists runs checks related to foreign * constraints and errors out if it is not possible to create one of the @@ -745,19 +805,6 @@ GetForeignKeysFromLocalTables(Oid relationId) } -/* - * HasForeignKeyToCitusLocalTable returns true if any of the foreign key constraints - * on the relation with relationId references to a citus local table. - */ -bool -HasForeignKeyToCitusLocalTable(Oid relationId) -{ - int flags = INCLUDE_REFERENCING_CONSTRAINTS | INCLUDE_CITUS_LOCAL_TABLES; - List *foreignKeyOidList = GetForeignKeyOids(relationId, flags); - return list_length(foreignKeyOidList) > 0; -} - - /* * HasForeignKeyToReferenceTable returns true if any of the foreign key * constraints on the relation with relationId references to a reference diff --git a/src/backend/distributed/commands/index.c b/src/backend/distributed/commands/index.c index 008f4fa90..5f1598510 100644 --- a/src/backend/distributed/commands/index.c +++ b/src/backend/distributed/commands/index.c @@ -1202,6 +1202,15 @@ ErrorIfUnsupportedIndexStmt(IndexStmt *createIndexStatement) "is currently unsupported"))); } + if (AllowUnsafeConstraints) + { + /* + * The user explicitly wants to allow the constraint without + * distribution column. + */ + return; + } + Var *partitionKey = DistPartitionKeyOrError(relationId); List *indexParameterList = createIndexStatement->indexParams; IndexElem *indexElement = NULL; diff --git a/src/backend/distributed/commands/multi_copy.c b/src/backend/distributed/commands/multi_copy.c index d795fddde..b5ac6a519 100644 --- a/src/backend/distributed/commands/multi_copy.c +++ b/src/backend/distributed/commands/multi_copy.c @@ -217,15 +217,6 @@ struct CopyShardState List *placementStateList; }; -/* ShardConnections represents a set of connections for each placement of a shard */ -typedef struct ShardConnections -{ - int64 shardId; - - /* list of MultiConnection structs */ - List *connectionList; -} ShardConnections; - /* * Represents the state for allowing copy via local @@ -1195,7 +1186,7 @@ ReportCopyError(MultiConnection *connection, PGresult *result) bool haveDetail = remoteDetail != NULL; ereport(ERROR, (errmsg("%s", remoteMessage), - haveDetail ? errdetail("%s", ApplyLogRedaction(remoteDetail)) : + haveDetail ? errdetail("%s", remoteDetail) : 0)); } else @@ -1206,7 +1197,7 @@ ReportCopyError(MultiConnection *connection, PGresult *result) ereport(ERROR, (errcode(ERRCODE_IO_ERROR), errmsg("failed to complete COPY on %s:%d", connection->hostname, connection->port), - errdetail("%s", ApplyLogRedaction(remoteMessage)))); + errdetail("%s", remoteMessage))); } } diff --git a/src/backend/distributed/commands/role.c b/src/backend/distributed/commands/role.c index 74d14751e..f61b00423 100644 --- a/src/backend/distributed/commands/role.c +++ b/src/backend/distributed/commands/role.c @@ -10,6 +10,8 @@ #include "postgres.h" +#include "pg_version_compat.h" + #include "distributed/pg_version_constants.h" #include "access/heapam.h" @@ -59,6 +61,7 @@ static char * CreateCreateOrAlterRoleCommand(const char *roleName, CreateRoleStmt *createRoleStmt, AlterRoleStmt *alterRoleStmt); static DefElem * makeDefElemInt(char *name, int value); +static DefElem * makeDefElemBool(char *name, bool value); static List * GenerateRoleOptionsList(HeapTuple tuple); static List * GenerateGrantRoleStmtsFromOptions(RoleSpec *roleSpec, List *options); static List * GenerateGrantRoleStmtsOfRole(Oid roleid); @@ -454,13 +457,13 @@ GenerateRoleOptionsList(HeapTuple tuple) Form_pg_authid role = ((Form_pg_authid) GETSTRUCT(tuple)); List *options = NIL; - options = lappend(options, makeDefElemInt("superuser", role->rolsuper)); - options = lappend(options, makeDefElemInt("createdb", role->rolcreatedb)); - options = lappend(options, makeDefElemInt("createrole", role->rolcreaterole)); - options = lappend(options, makeDefElemInt("inherit", role->rolinherit)); - options = lappend(options, makeDefElemInt("canlogin", role->rolcanlogin)); - options = lappend(options, makeDefElemInt("isreplication", role->rolreplication)); - options = lappend(options, makeDefElemInt("bypassrls", role->rolbypassrls)); + options = lappend(options, makeDefElemBool("superuser", role->rolsuper)); + options = lappend(options, makeDefElemBool("createdb", role->rolcreatedb)); + options = lappend(options, makeDefElemBool("createrole", role->rolcreaterole)); + options = lappend(options, makeDefElemBool("inherit", role->rolinherit)); + options = lappend(options, makeDefElemBool("canlogin", role->rolcanlogin)); + options = lappend(options, makeDefElemBool("isreplication", role->rolreplication)); + options = lappend(options, makeDefElemBool("bypassrls", role->rolbypassrls)); options = lappend(options, makeDefElemInt("connectionlimit", role->rolconnlimit)); /* load password from heap tuple, use NULL if not set */ @@ -616,6 +619,16 @@ makeDefElemInt(char *name, int value) } +/* + * makeDefElemBool creates a DefElem with boolean typed value with -1 as location. + */ +static DefElem * +makeDefElemBool(char *name, bool value) +{ + return makeDefElem(name, (Node *) makeBoolean(value), -1); +} + + /* * GetDatabaseNameFromDbRoleSetting performs a lookup, and finds the database name * associated DbRoleSetting Tuple diff --git a/src/backend/distributed/commands/schema.c b/src/backend/distributed/commands/schema.c index 3a5ee5bde..66189f39e 100644 --- a/src/backend/distributed/commands/schema.c +++ b/src/backend/distributed/commands/schema.c @@ -151,6 +151,11 @@ List * PreprocessGrantOnSchemaStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext) { + if (!ShouldPropagate()) + { + return NIL; + } + GrantStmt *stmt = castNode(GrantStmt, node); Assert(stmt->objtype == OBJECT_SCHEMA); diff --git a/src/backend/distributed/commands/table.c b/src/backend/distributed/commands/table.c index bc897eec6..689176890 100644 --- a/src/backend/distributed/commands/table.c +++ b/src/backend/distributed/commands/table.c @@ -54,6 +54,12 @@ /* controlled via GUC, should be accessed via GetEnableLocalReferenceForeignKeys() */ bool EnableLocalReferenceForeignKeys = true; +/* + * GUC that controls whether to allow unique/exclude constraints without + * distribution column. + */ +bool AllowUnsafeConstraints = false; + /* Local functions forward declarations for unsupported command checks */ static void PostprocessCreateTableStmtForeignKeys(CreateStmt *createStatement); static void PostprocessCreateTableStmtPartitionOf(CreateStmt *createStatement, @@ -2573,6 +2579,16 @@ ErrorIfUnsupportedConstraint(Relation relation, char distributionMethod, errhint("Consider using hash partitioning."))); } + if (AllowUnsafeConstraints) + { + /* + * The user explicitly wants to allow the constraint without + * distribution column. + */ + index_close(indexDesc, NoLock); + continue; + } + int attributeCount = indexInfo->ii_NumIndexAttrs; AttrNumber *attributeNumberArray = indexInfo->ii_IndexAttrNumbers; diff --git a/src/backend/distributed/commands/trigger.c b/src/backend/distributed/commands/trigger.c index 299ffcc32..ffb0ab5ae 100644 --- a/src/backend/distributed/commands/trigger.c +++ b/src/backend/distributed/commands/trigger.c @@ -182,8 +182,17 @@ GetExplicitTriggerIdList(Oid relationId) * Note that we mark truncate trigger that we create on citus tables as * internal. Hence, below we discard citus_truncate_trigger as well as * the implicit triggers created by postgres for foreign key validation. + * + * Pre PG15, tgisinternal is true for a "child" trigger on a partition + * cloned from the trigger on the parent. + * In PG15, tgisinternal is false in that case. However, we don't want to + * create this trigger on the partition since it will create a conflict + * when we try to attach the partition to the parent table: + * ERROR: trigger "..." for relation "{partition_name}" already exists + * Hence we add an extra check on whether the parent id is invalid to + * make sure this is not a child trigger */ - if (!triggerForm->tgisinternal) + if (!triggerForm->tgisinternal && (triggerForm->tgparentid == InvalidOid)) { triggerIdList = lappend_oid(triggerIdList, triggerForm->oid); } diff --git a/src/backend/distributed/commands/utility_hook.c b/src/backend/distributed/commands/utility_hook.c index 9b0a79e22..8bfd5df44 100644 --- a/src/backend/distributed/commands/utility_hook.c +++ b/src/backend/distributed/commands/utility_hook.c @@ -1415,53 +1415,6 @@ set_indexsafe_procflags(void) #endif -/* - * CreateCustomDDLTaskList creates a DDLJob which will apply a command to all placements - * of shards of a distributed table. The command to be applied is generated by the - * TableDDLCommand structure passed in. - */ -DDLJob * -CreateCustomDDLTaskList(Oid relationId, TableDDLCommand *command) -{ - List *taskList = NIL; - List *shardIntervalList = LoadShardIntervalList(relationId); - uint64 jobId = INVALID_JOB_ID; - Oid namespace = get_rel_namespace(relationId); - char *namespaceName = get_namespace_name(namespace); - int taskId = 1; - - /* lock metadata before getting placement lists */ - LockShardListMetadata(shardIntervalList, ShareLock); - - ShardInterval *shardInterval = NULL; - foreach_ptr(shardInterval, shardIntervalList) - { - uint64 shardId = shardInterval->shardId; - - char *commandStr = GetShardedTableDDLCommand(command, shardId, namespaceName); - - Task *task = CitusMakeNode(Task); - task->jobId = jobId; - task->taskId = taskId++; - task->taskType = DDL_TASK; - SetTaskQueryString(task, commandStr); - task->replicationModel = REPLICATION_MODEL_INVALID; - task->dependentTaskList = NULL; - task->anchorShardId = shardId; - task->taskPlacementList = ActiveShardPlacementList(shardId); - - taskList = lappend(taskList, task); - } - - DDLJob *ddlJob = palloc0(sizeof(DDLJob)); - ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId); - ddlJob->metadataSyncCommand = GetTableDDLCommand(command); - ddlJob->taskList = taskList; - - return ddlJob; -} - - /* * SetSearchPathToCurrentSearchPathCommand generates a command which can * set the search path to the exact same search path that the issueing node diff --git a/src/backend/distributed/connection/remote_commands.c b/src/backend/distributed/connection/remote_commands.c index a7e96efed..c5aeefd51 100644 --- a/src/backend/distributed/connection/remote_commands.c +++ b/src/backend/distributed/connection/remote_commands.c @@ -258,10 +258,6 @@ ReportConnectionError(MultiConnection *connection, int elevel) if (messageDetail) { - /* - * We don't use ApplyLogRedaction(messageDetail) as we expect any error - * detail that requires log reduction should have done it locally. - */ ereport(elevel, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("connection to the remote node %s:%d failed with the " "following error: %s", nodeName, nodePort, @@ -315,7 +311,7 @@ ReportResultError(MultiConnection *connection, PGresult *result, int elevel) ereport(elevel, (errcode(sqlState), errmsg("%s", messagePrimary), messageDetail ? - errdetail("%s", ApplyLogRedaction(messageDetail)) : 0, + errdetail("%s", messageDetail) : 0, messageHint ? errhint("%s", messageHint) : 0, messageContext ? errcontext("%s", messageContext) : 0, errcontext("while executing command on %s:%d", @@ -349,7 +345,7 @@ LogRemoteCommand(MultiConnection *connection, const char *command) return; } - ereport(NOTICE, (errmsg("issuing %s", ApplyLogRedaction(command)), + ereport(NOTICE, (errmsg("issuing %s", command), errdetail("on server %s@%s:%d connectionId: %ld", connection->user, connection->hostname, connection->port, connection->connectionId))); diff --git a/src/backend/distributed/connection/worker_log_messages.c b/src/backend/distributed/connection/worker_log_messages.c index b822f5e83..9c240620e 100644 --- a/src/backend/distributed/connection/worker_log_messages.c +++ b/src/backend/distributed/connection/worker_log_messages.c @@ -173,7 +173,7 @@ DefaultCitusNoticeReceiver(void *arg, const PGresult *result) ereport(logLevel, (errcode(sqlState), - errmsg("%s", ApplyLogRedaction(trimmedMessage)), + errmsg("%s", trimmedMessage), errdetail("from %s:%d", nodeName, nodePort))); } diff --git a/src/backend/distributed/deparser/deparse_function_stmts.c b/src/backend/distributed/deparser/deparse_function_stmts.c index 48f993425..524a1928d 100644 --- a/src/backend/distributed/deparser/deparse_function_stmts.c +++ b/src/backend/distributed/deparser/deparse_function_stmts.c @@ -196,7 +196,7 @@ AppendDefElem(StringInfo buf, DefElem *def) static void AppendDefElemStrict(StringInfo buf, DefElem *def) { - if (intVal(def->arg) == 1) + if (boolVal(def->arg)) { appendStringInfo(buf, " STRICT"); } @@ -223,7 +223,7 @@ AppendDefElemVolatility(StringInfo buf, DefElem *def) static void AppendDefElemLeakproof(StringInfo buf, DefElem *def) { - if (intVal(def->arg) == 0) + if (!boolVal(def->arg)) { appendStringInfo(buf, " NOT"); } @@ -237,7 +237,7 @@ AppendDefElemLeakproof(StringInfo buf, DefElem *def) static void AppendDefElemSecurity(StringInfo buf, DefElem *def) { - if (intVal(def->arg) == 0) + if (!boolVal(def->arg)) { appendStringInfo(buf, " SECURITY INVOKER"); } diff --git a/src/backend/distributed/deparser/deparse_role_stmts.c b/src/backend/distributed/deparser/deparse_role_stmts.c index 2af073e89..0e9b300bb 100644 --- a/src/backend/distributed/deparser/deparse_role_stmts.c +++ b/src/backend/distributed/deparser/deparse_role_stmts.c @@ -13,6 +13,8 @@ #include "postgres.h" +#include "pg_version_compat.h" + #include "distributed/citus_ruleutils.h" #include "distributed/deparser.h" #include "lib/stringinfo.h" @@ -98,59 +100,59 @@ AppendRoleOption(StringInfo buf, ListCell *optionCell) { DefElem *option = (DefElem *) lfirst(optionCell); - if (strcmp(option->defname, "superuser") == 0 && intVal(option->arg)) + if (strcmp(option->defname, "superuser") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " SUPERUSER"); } - else if (strcmp(option->defname, "superuser") == 0 && !intVal(option->arg)) + else if (strcmp(option->defname, "superuser") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOSUPERUSER"); } - else if (strcmp(option->defname, "createdb") == 0 && intVal(option->arg)) + else if (strcmp(option->defname, "createdb") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " CREATEDB"); } - else if (strcmp(option->defname, "createdb") == 0 && !intVal(option->arg)) + else if (strcmp(option->defname, "createdb") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOCREATEDB"); } - else if (strcmp(option->defname, "createrole") == 0 && intVal(option->arg)) + else if (strcmp(option->defname, "createrole") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " CREATEROLE"); } - else if (strcmp(option->defname, "createrole") == 0 && !intVal(option->arg)) + else if (strcmp(option->defname, "createrole") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOCREATEROLE"); } - else if (strcmp(option->defname, "inherit") == 0 && intVal(option->arg)) + else if (strcmp(option->defname, "inherit") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " INHERIT"); } - else if (strcmp(option->defname, "inherit") == 0 && !intVal(option->arg)) + else if (strcmp(option->defname, "inherit") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOINHERIT"); } - else if (strcmp(option->defname, "canlogin") == 0 && intVal(option->arg)) + else if (strcmp(option->defname, "canlogin") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " LOGIN"); } - else if (strcmp(option->defname, "canlogin") == 0 && !intVal(option->arg)) + else if (strcmp(option->defname, "canlogin") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOLOGIN"); } - else if (strcmp(option->defname, "isreplication") == 0 && intVal(option->arg)) + else if (strcmp(option->defname, "isreplication") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " REPLICATION"); } - else if (strcmp(option->defname, "isreplication") == 0 && !intVal(option->arg)) + else if (strcmp(option->defname, "isreplication") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOREPLICATION"); } - else if (strcmp(option->defname, "bypassrls") == 0 && intVal(option->arg)) + else if (strcmp(option->defname, "bypassrls") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " BYPASSRLS"); } - else if (strcmp(option->defname, "bypassrls") == 0 && !intVal(option->arg)) + else if (strcmp(option->defname, "bypassrls") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOBYPASSRLS"); } diff --git a/src/backend/distributed/deparser/ruleutils_14.c b/src/backend/distributed/deparser/ruleutils_14.c index 3fa6b1986..bde8e1b23 100644 --- a/src/backend/distributed/deparser/ruleutils_14.c +++ b/src/backend/distributed/deparser/ruleutils_14.c @@ -18,8 +18,7 @@ #include "pg_config.h" -/* We should drop PG 15 support from this file, this is only for testing purposes until #6085 is merged. */ -#if (PG_VERSION_NUM >= PG_VERSION_14) && (PG_VERSION_NUM <= PG_VERSION_15) +#if (PG_VERSION_NUM >= PG_VERSION_14) && (PG_VERSION_NUM < PG_VERSION_15) #include "postgres.h" diff --git a/src/backend/distributed/deparser/ruleutils_15.c b/src/backend/distributed/deparser/ruleutils_15.c new file mode 100644 index 000000000..642d27fa1 --- /dev/null +++ b/src/backend/distributed/deparser/ruleutils_15.c @@ -0,0 +1,9445 @@ +/*------------------------------------------------------------------------- + * + * ruleutils_15.c + * Functions to convert stored expressions/querytrees back to + * source text + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/distributed/deparser/ruleutils_15.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_15) && (PG_VERSION_NUM < PG_VERSION_16) + +#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/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/parse_relation.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 */ + /* Special namespace representing a function signature: */ + char *funcname; + int numargs; + char **argnames; +} 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 Plan *find_recursive_union(deparse_namespace *dpns, + WorkTableScan *wtscan); +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, bool colNamesVisible, + int prettyFlags, int wrapColumn, int startIndent); +static void get_query_def_extended(Query *query, StringInfo buf, + List *parentnamespace, Oid distrelid, int64 shardid, + TupleDesc resultDesc, bool colNamesVisible, + 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, bool colNamesVisible); +static void get_insert_query_def(Query *query, deparse_context *context, + bool colNamesVisible); +static void get_update_query_def(Query *query, deparse_context *context, + bool colNamesVisible); +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, + bool colNamesVisible); +static void get_utility_query_def(Query *query, deparse_context *context); +static void get_basic_select_query(Query *query, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible); +static void get_target_list(List *targetList, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible); +static void get_setop_query(Node *setOp, Query *query, + deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible); +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_list_toplevel(List *lst, 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_proc_expr(CallStmt *stmt, 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 bool get_func_sql_syntax(FuncExpr *expr, 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_json_constructor(JsonConstructorExpr *ctor, + deparse_context *context, bool showimplicit); +static void get_json_agg_constructor(JsonConstructorExpr *ctor, + deparse_context *context, + const char *funcname, + bool is_json_objectagg); +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); +static void get_json_path_spec(Node *path_spec, deparse_context *context, + bool showimplicit); +static void get_json_table_columns(TableFunc *tf, JsonTableParent *node, + deparse_context *context, bool showimplicit); + +#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, false, 0, WRAP_COLUMN_DEFAULT, 0); +} + +/* + * get_merged_argument_list merges both the IN and OUT arguments lists into one and + * also eliminates the INOUT duplicates(present in both the lists). After merging both + * the lists, it returns all the named-arguments in a list(mergedNamedArgList) along + * with their types(mergedNamedArgTypes), final argument list(mergedArgumentList), and + * the total number of arguments(totalArguments). + */ +bool +get_merged_argument_list(CallStmt *stmt, List **mergedNamedArgList, + Oid **mergedNamedArgTypes, + List **mergedArgumentList, + int *totalArguments) +{ + + Oid functionOid = stmt->funcexpr->funcid; + List *namedArgList = NIL; + List *finalArgumentList = NIL; + Oid finalArgTypes[FUNC_MAX_ARGS]; + Oid *argTypes = NULL; + char *argModes = NULL; + char **argNames = NULL; + int argIndex = 0; + + HeapTuple proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid)); + if (!HeapTupleIsValid(proctup)) + { + elog(ERROR, "cache lookup failed for function %u", functionOid); + } + + int defArgs = get_func_arg_info(proctup, &argTypes, &argNames, &argModes); + ReleaseSysCache(proctup); + + if (argModes == NULL) + { + /* No OUT arguments */ + return false; + } + + /* + * Passed arguments Includes IN, OUT, INOUT (in both the lists) and VARIADIC arguments, + * which means INOUT arguments are double counted. + */ + int numberOfArgs = list_length(stmt->funcexpr->args) + list_length(stmt->outargs); + int totalInoutArgs = 0; + + /* Let's count INOUT arguments from the defined number of arguments */ + for (argIndex=0; argIndex < defArgs; ++argIndex) + { + if (argModes[argIndex] == PROARGMODE_INOUT) + totalInoutArgs++; + } + + /* Remove the duplicate INOUT counting */ + numberOfArgs = numberOfArgs - totalInoutArgs; + + ListCell *inArgCell = list_head(stmt->funcexpr->args); + ListCell *outArgCell = list_head(stmt->outargs); + + for (argIndex=0; argIndex < numberOfArgs; ++argIndex) + { + switch (argModes[argIndex]) + { + case PROARGMODE_IN: + case PROARGMODE_VARIADIC: + { + Node *arg = (Node *) lfirst(inArgCell); + + if (IsA(arg, NamedArgExpr)) + namedArgList = lappend(namedArgList, ((NamedArgExpr *) arg)->name); + finalArgTypes[argIndex] = exprType(arg); + finalArgumentList = lappend(finalArgumentList, arg); + inArgCell = lnext(stmt->funcexpr->args, inArgCell); + break; + } + + case PROARGMODE_OUT: + { + Node *arg = (Node *) lfirst(outArgCell); + + if (IsA(arg, NamedArgExpr)) + namedArgList = lappend(namedArgList, ((NamedArgExpr *) arg)->name); + finalArgTypes[argIndex] = exprType(arg); + finalArgumentList = lappend(finalArgumentList, arg); + outArgCell = lnext(stmt->outargs, outArgCell); + break; + } + + case PROARGMODE_INOUT: + { + Node *arg = (Node *) lfirst(inArgCell); + + if (IsA(arg, NamedArgExpr)) + namedArgList = lappend(namedArgList, ((NamedArgExpr *) arg)->name); + finalArgTypes[argIndex] = exprType(arg); + finalArgumentList = lappend(finalArgumentList, arg); + inArgCell = lnext(stmt->funcexpr->args, inArgCell); + outArgCell = lnext(stmt->outargs, outArgCell); + break; + } + + case PROARGMODE_TABLE: + default: + { + elog(ERROR, "Unhandled procedure argument mode[%d]", argModes[argIndex]); + break; + } + } + } + + /* + * After eliminating INOUT duplicates and merging OUT arguments, we now + * have the final list of arguments. + */ + if (defArgs != list_length(finalArgumentList)) + { + elog(ERROR, "Insufficient number of args passed[%d] for function[%s]", + list_length(finalArgumentList), + get_func_name(functionOid)); + } + + if (list_length(finalArgumentList) > FUNC_MAX_ARGS) + { + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("too many arguments[%d] for function[%s]", + list_length(finalArgumentList), + get_func_name(functionOid)))); + } + + *mergedNamedArgList = namedArgList; + *mergedNamedArgTypes = finalArgTypes; + *mergedArgumentList = finalArgumentList; + *totalArguments = numberOfArgs; + + return true; +} +/* + * 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. + */ + 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_STRINGS | 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 (;;) + { + 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; + + /* + * Construct an array of the current "real" column names of the RTE. + * real_colnames[] will be indexed by physical column number, with NULL + * entries for dropped columns. + */ + if (rte->rtekind == RTE_RELATION) + { + /* 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 get the column names from eref or expandRTE() */ + List *colnames; + ListCell *lc; + + /* + * Functions returning composites have the annoying property that some + * of the composite type's columns might have been dropped since the + * query was parsed. If possible, use expandRTE() to handle that + * case, since it has the tedious logic needed to find out about + * dropped columns. However, if we're explaining a plan, then we + * don't have rte->functions because the planner thinks that won't be + * needed later, and that breaks expandRTE(). So in that case we have + * to rely on rte->eref, which may lead us to report a dropped + * column's old name; that seems close enough for EXPLAIN's purposes. + * + * For non-RELATION, non-FUNCTION RTEs, we can just look at rte->eref, + * which should be sufficiently up-to-date: no other RTE types can + * have columns get dropped from under them after parsing. + */ + if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL) + { + /* Since we're not creating Vars, rtindex etc. don't matter */ + expandRTE(rte, 1, 0, -1, true /* include dropped */ , + &colnames, NULL); + } + else + colnames = rte->eref->colnames; + + ncolumns = list_length(colnames); + real_colnames = (char **) palloc(ncolumns * sizeof(char *)); + + i = 0; + foreach(lc, colnames) + { + /* + * If the column name we find here is an empty string, then it's a + * dropped column, so change to NULL. + */ + 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 (;;) + { + 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. + */ + if (IsA(plan, Append)) + dpns->outer_plan = linitial(((Append *) plan)->appendplans); + else if (IsA(plan, MergeAppend)) + dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans); + 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 a WorkTableScan, locate the parent RecursiveUnion plan node and use + * that as INNER referent. + * + * For MERGE, make the inner tlist point to the merge source tlist, which + * is same as the targetlist that the ModifyTable's source plan provides. + * 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, WorkTableScan)) + dpns->inner_plan = find_recursive_union(dpns, + (WorkTableScan *) plan); + else if (IsA(plan, ModifyTable)) + dpns->inner_plan = plan; + else + dpns->inner_plan = innerPlan(plan); + + if (IsA(plan, ModifyTable)) + { + if (((ModifyTable *) plan)->operation == CMD_MERGE) + dpns->inner_tlist = dpns->outer_tlist; + else + 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; +} + +/* + * Locate the ancestor plan node that is the RecursiveUnion generating + * the WorkTableScan's work table. We can match on wtParam, since that + * should be unique within the plan tree. + */ +static Plan * +find_recursive_union(deparse_namespace *dpns, WorkTableScan *wtscan) +{ + ListCell *lc; + + foreach(lc, dpns->ancestors) + { + Plan *ancestor = (Plan *) lfirst(lc); + + if (IsA(ancestor, RecursiveUnion) && + ((RecursiveUnion *) ancestor)->wtParam == wtscan->wtParam) + return ancestor; + } + elog(ERROR, "could not find RecursiveUnion for WorkTableScan with wtParam %d", + wtscan->wtParam); + return NULL; +} + +/* + * 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, + false, + 0, WRAP_COLUMN_DEFAULT, 0); +} + + +/* ---------- + * get_query_def - Parse back one query parsetree + * + * query: parsetree to be displayed + * buf: output text is appended to buf + * parentnamespace: list (initially empty) of outer-level deparse_namespace's + * resultDesc: if not NULL, the output tuple descriptor for the view + * represented by a SELECT query. We use the column names from it + * to label SELECT output columns, in preference to names in the query + * colNamesVisible: true if the surrounding context cares about the output + * column names at all (as, for example, an EXISTS() context does not); + * when false, we can suppress dummy column labels such as "?column?" + * prettyFlags: bitmask of PRETTYFLAG_XXX options + * wrapColumn: maximum line length, or -1 to disable wrapping + * startIndent: initial indentation amount + * ---------- + */ +static void +get_query_def(Query *query, StringInfo buf, List *parentnamespace, + TupleDesc resultDesc, bool colNamesVisible, + int prettyFlags, int wrapColumn, int startIndent) +{ + get_query_def_extended(query, buf, parentnamespace, InvalidOid, 0, resultDesc, + colNamesVisible, + 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, + bool colNamesVisible, + 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, colNamesVisible); + break; + + case CMD_UPDATE: + get_update_query_def(query, &context, colNamesVisible); + break; + + case CMD_INSERT: + get_insert_query_def(query, &context, colNamesVisible); + break; + + case CMD_DELETE: + get_delete_query_def(query, &context, colNamesVisible); + 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, + true, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + if (PRETTY_INDENT(context)) + appendContextKeyword(context, "", 0, 0, 0); + appendStringInfoChar(buf, ')'); + + if (cte->search_clause) + { + bool first = true; + ListCell *lc; + + appendStringInfo(buf, " SEARCH %s FIRST BY ", + cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH"); + + foreach(lc, cte->search_clause->search_col_list) + { + if (first) + first = false; + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, + quote_identifier(strVal(lfirst(lc)))); + } + + appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column)); + } + + if (cte->cycle_clause) + { + bool first = true; + ListCell *lc; + + appendStringInfoString(buf, " CYCLE "); + + foreach(lc, cte->cycle_clause->cycle_col_list) + { + if (first) + first = false; + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, + quote_identifier(strVal(lfirst(lc)))); + } + + appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column)); + + { + Const *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value); + Const *cmd = castNode(Const, cte->cycle_clause->cycle_mark_default); + + if (!(cmv->consttype == BOOLOID && !cmv->constisnull && DatumGetBool(cmv->constvalue) == true && + cmd->consttype == BOOLOID && !cmd->constisnull && DatumGetBool(cmd->constvalue) == false)) + { + appendStringInfoString(buf, " TO "); + get_rule_expr(cte->cycle_clause->cycle_mark_value, context, false); + appendStringInfoString(buf, " DEFAULT "); + get_rule_expr(cte->cycle_clause->cycle_mark_default, context, false); + } + } + + appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column)); + } + + 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, bool colNamesVisible) +{ + 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, + colNamesVisible); + /* ORDER BY clauses must be simple in this case */ + force_colno = true; + } + else + { + get_basic_select_query(query, context, resultDesc, colNamesVisible); + 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); + appendStringInfoString(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, bool colNamesVisible) +{ + 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 + */ + if (query->isReturn) + appendStringInfoString(buf, "RETURN"); + else + 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, colNamesVisible); + + /* 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); + if (query->groupDistinct) + appendStringInfoString(buf, "DISTINCT "); + + 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. + * + * resultDesc and colNamesVisible are as for get_query_def() + * ---------- + */ +static void +get_target_list(List *targetList, deparse_context *context, + TupleDesc resultDesc, bool colNamesVisible) +{ + 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); + + /* + * When colNamesVisible is true, we should always show the + * assigned column name explicitly. Otherwise, show it only if + * it's not FigureColname's fallback. + */ + attname = colNamesVisible ? NULL : "?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, bool colNamesVisible) +{ + 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, + colNamesVisible, + 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, colNamesVisible); + + 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, false); + + 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) + || IsA(expr, JsonConstructorExpr)); + + 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, + bool colNamesVisible) +{ + 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, context->namespaces, NULL, + false, + 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_list_toplevel(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, colNamesVisible); + } +} + + +/* ---------- + * get_update_query_def - Parse back an UPDATE parsetree + * ---------- + */ +static void +get_update_query_def(Query *query, deparse_context *context, + bool colNamesVisible) +{ + 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, colNamesVisible); + } +} + + +/* ---------- + * 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, + bool colNamesVisible) +{ + 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, colNamesVisible); + } +} + + +/* ---------- + * 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; + int 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) + { + + int 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 we find a Var referencing a dropped column, it seems better to + * print something (anything) than to fail. In general this should + * not happen, but it used to be possible for some cases involving + * functions returning named composite types, and perhaps there are + * still bugs out there. + */ + if (attname == NULL) + attname = "?dropped?column?"; + } + 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; + int 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 places we'd see a Var directly + * referencing a CTE RTE are in CteScan or WorkTableScan + * plan nodes. For those cases, set_deparse_plan arranged + * for dpns->inner_plan to be the plan node that emits the + * CTE or RecursiveUnion result, and we can look at its + * 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 = lfirst_node(SubPlan, 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, GroupingFunc, 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, GroupingFunc) || + 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; + } + + /* + * If it's an external parameter, see if the outermost namespace provides + * function argument names. + */ + if (param->paramkind == PARAM_EXTERN && context->namespaces != NIL) + { + dpns = llast(context->namespaces); + if (dpns->argnames && + param->paramid > 0 && + param->paramid <= dpns->numargs) + { + char *argname = dpns->argnames[param->paramid - 1]; + + if (argname) + { + bool should_qualify = false; + ListCell *lc; + + /* + * Qualify the parameter name if there are any other deparse + * namespaces with range tables. This avoids qualifying in + * trivial cases like "RETURN a + b", but makes it safe in all + * other cases. + */ + foreach(lc, context->namespaces) + { + deparse_namespace *dp_ns = lfirst(lc); + + if (list_length(dp_ns->rtable_names) > 0) + { + should_qualify = true; + break; + } + } + if (should_qualify) + { + appendStringInfoString(context->buf, quote_identifier(dpns->funcname)); + appendStringInfoChar(context->buf, '.'); + } + + appendStringInfoString(context->buf, quote_identifier(argname)); + 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_GroupingFunc: + case T_WindowFunc: + case T_FuncExpr: + case T_JsonConstructorExpr: + case T_JsonExpr: + /* 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); + + case T_FieldStore: + + /* + * treat like FieldSelect (probably doesn't matter) + */ + return !IsA(parentNode, FieldStore); + + 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: + case T_JsonIsPredicate: + 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_GroupingFunc: /* own parentheses */ + case T_WindowFunc: /* own parentheses */ + case T_CaseExpr: /* other separators */ + case T_JsonExpr: /* own parentheses */ + 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_GroupingFunc: /* own parentheses */ + case T_WindowFunc: /* own parentheses */ + case T_CaseExpr: /* other separators */ + return true; + default: + return false; + } + + case T_JsonValueExpr: + /* maybe simple, check args */ + return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr, + node, prettyFlags); + + 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_json_path_spec - Parse back a JSON path specification + */ +static void +get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit) +{ + if (IsA(path_spec, Const)) + get_const_expr((Const *) path_spec, context, -1); + else + get_rule_expr(path_spec, context, showimplicit); +} + +/* + * get_json_format - Parse back a JsonFormat node + */ +static void +get_json_format(JsonFormat *format, StringInfo buf) +{ + if (format->format_type == JS_FORMAT_DEFAULT) + return; + + appendStringInfoString(buf, + format->format_type == JS_FORMAT_JSONB ? + " FORMAT JSONB" : " FORMAT JSON"); + + if (format->encoding != JS_ENC_DEFAULT) + { + const char *encoding = + format->encoding == JS_ENC_UTF16 ? "UTF16" : + format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8"; + + appendStringInfo(buf, " ENCODING %s", encoding); + } +} + +/* + * get_json_returning - Parse back a JsonReturning structure + */ +static void +get_json_returning(JsonReturning *returning, StringInfo buf, + bool json_format_by_default) +{ + if (!OidIsValid(returning->typid)) + return; + + appendStringInfo(buf, " RETURNING %s", + format_type_with_typemod(returning->typid, + returning->typmod)); + + if (!json_format_by_default || + returning->format->format_type != + (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON)) + get_json_format(returning->format, buf); +} + +static void +get_json_behavior(JsonBehavior *behavior, deparse_context *context, + const char *on) +{ + /* + * The order of array elements must correspond to the order of + * JsonBehaviorType members. + */ + const char *behavior_names[] = + { + " NULL", + " ERROR", + " EMPTY", + " TRUE", + " FALSE", + " UNKNOWN", + " EMPTY ARRAY", + " EMPTY OBJECT", + " DEFAULT " + }; + + if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names)) + elog(ERROR, "invalid json behavior type: %d", behavior->btype); + + appendStringInfoString(context->buf, behavior_names[behavior->btype]); + + if (behavior->btype == JSON_BEHAVIOR_DEFAULT) + get_rule_expr(behavior->default_expr, context, false); + + appendStringInfo(context->buf, " ON %s", on); +} + +/* + * get_json_expr_options + * + * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS and + * JSON_TABLE columns. + */ +static void +get_json_expr_options(JsonExpr *jsexpr, deparse_context *context, + JsonBehaviorType default_behavior) +{ + if (jsexpr->op == JSON_QUERY_OP) + { + if (jsexpr->wrapper == JSW_CONDITIONAL) + appendStringInfo(context->buf, " WITH CONDITIONAL WRAPPER"); + else if (jsexpr->wrapper == JSW_UNCONDITIONAL) + appendStringInfo(context->buf, " WITH UNCONDITIONAL WRAPPER"); + + if (jsexpr->omit_quotes) + appendStringInfo(context->buf, " OMIT QUOTES"); + } + + if (jsexpr->op != JSON_EXISTS_OP && + jsexpr->on_empty->btype != default_behavior) + get_json_behavior(jsexpr->on_empty, context, "EMPTY"); + + if (jsexpr->on_error->btype != default_behavior) + get_json_behavior(jsexpr->on_error, context, "ERROR"); +} + +/* ---------- + * 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; + + switch (expr->boolop) + { + case AND_EXPR: + if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); + get_rule_expr_paren(first_arg, context, + false, node); + for_each_from(arg, expr->args, 1) + { + appendStringInfoString(buf, " AND "); + get_rule_expr_paren((Node *) lfirst(arg), context, + false, node); + } + 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); + for_each_from(arg, expr->args, 1) + { + appendStringInfoString(buf, " OR "); + get_rule_expr_paren((Node *) lfirst(arg), context, + false, node); + } + 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; + + /* + * This case cannot be reached in normal usage, since no + * AlternativeSubPlan can appear either in parsetrees or + * finished plan trees. We keep it just in case somebody + * wants to use this code to print planner data structures. + */ + 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; + + /* + * SQL99 allows "ROW" to be omitted when there is more than + * one column, but for simplicity we always print it. Within + * a ROW expression, whole-row Vars need special treatment, so + * use get_rule_list_toplevel. + */ + appendStringInfoString(buf, "(ROW("); + get_rule_list_toplevel(rcexpr->largs, context, true); + + /* + * 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)))); + get_rule_list_toplevel(rcexpr->rargs, context, true); + 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 = lfirst_node(Const, 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_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + get_rule_expr((Node *) jve->raw_expr, context, false); + get_json_format(jve->format, context->buf); + } + break; + + case T_JsonConstructorExpr: + get_json_constructor((JsonConstructorExpr *) node, context, false); + break; + + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, '('); + + get_rule_expr_paren(pred->expr, context, true, node); + + appendStringInfoString(context->buf, " IS JSON"); + + /* TODO: handle FORMAT clause */ + + switch (pred->item_type) + { + case JS_TYPE_SCALAR: + appendStringInfoString(context->buf, " SCALAR"); + break; + case JS_TYPE_ARRAY: + appendStringInfoString(context->buf, " ARRAY"); + break; + case JS_TYPE_OBJECT: + appendStringInfoString(context->buf, " OBJECT"); + break; + default: + break; + } + + if (pred->unique_keys) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, ')'); + } + break; + + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + switch (jexpr->op) + { + case JSON_QUERY_OP: + appendStringInfoString(buf, "JSON_QUERY("); + break; + case JSON_VALUE_OP: + appendStringInfoString(buf, "JSON_VALUE("); + break; + case JSON_EXISTS_OP: + appendStringInfoString(buf, "JSON_EXISTS("); + break; + default: + elog(ERROR, "unexpected JsonExpr type: %d", jexpr->op); + break; + } + + get_rule_expr(jexpr->formatted_expr, context, showimplicit); + + appendStringInfoString(buf, ", "); + + get_json_path_spec(jexpr->path_spec, context, showimplicit); + + if (jexpr->passing_values) + { + ListCell *lc1, + *lc2; + bool needcomma = false; + + appendStringInfoString(buf, " PASSING "); + + forboth(lc1, jexpr->passing_names, + lc2, jexpr->passing_values) + { + if (needcomma) + appendStringInfoString(buf, ", "); + needcomma = true; + + get_rule_expr((Node *) lfirst(lc2), context, showimplicit); + appendStringInfo(buf, " AS %s", + ((String *) lfirst_node(String, lc1))->sval); + } + } + + if (jexpr->op != JSON_EXISTS_OP || + jexpr->returning->typid != BOOLOID) + get_json_returning(jexpr->returning, context->buf, + jexpr->op == JSON_QUERY_OP); + + get_json_expr_options(jexpr, context, + jexpr->op == JSON_EXISTS_OP ? + JSON_BEHAVIOR_FALSE : JSON_BEHAVIOR_NULL); + + appendStringInfoString(buf, ")"); + } + 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_proc_expr((CallStmt *) node, 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_list_toplevel - Parse back a list of toplevel expressions + * + * Apply get_rule_expr_toplevel() to each element of a List. + * + * This adds commas between the expressions, but caller is responsible + * for printing surrounding decoration. + */ +static void +get_rule_list_toplevel(List *lst, deparse_context *context, + bool showimplicit) +{ + const char *sep; + ListCell *lc; + + sep = ""; + foreach(lc, lst) + { + Node *e = (Node *) lfirst(lc); + + appendStringInfoString(context->buf, sep); + get_rule_expr_toplevel(e, context, showimplicit); + sep = ", "; + } +} + +/* + * 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 || + ((FuncExpr *) node)->funcformat == COERCE_SQL_SYNTAX); + case T_NullIfExpr: + case T_CoalesceExpr: + case T_MinMaxExpr: + case T_SQLValueFunction: + case T_XmlExpr: + case T_JsonExpr: + /* 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 + { + /* prefix operator */ + Node *arg = (Node *) linitial(args); + + appendStringInfo(buf, "%s ", + generate_operator_name(opno, + InvalidOid, + exprType(arg))); + get_rule_expr_paren(arg, context, true, (Node *) expr); + } + 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; + } + + /* + * If the function was called using one of the SQL spec's random special + * syntaxes, try to reproduce that. If we don't recognize the function, + * fall through. + */ + if (expr->funcformat == COERCE_SQL_SYNTAX) + { + if (get_func_sql_syntax(expr, context)) + 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, ')'); +} + +static void +get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf) +{ + if (ctor->absent_on_null) + { + if (ctor->type == JSCTOR_JSON_OBJECT || + ctor->type == JSCTOR_JSON_OBJECTAGG) + appendStringInfoString(buf, " ABSENT ON NULL"); + } + else + { + if (ctor->type == JSCTOR_JSON_ARRAY || + ctor->type == JSCTOR_JSON_ARRAYAGG) + appendStringInfoString(buf, " NULL ON NULL"); + } + + if (ctor->unique) + appendStringInfoString(buf, " WITH UNIQUE KEYS"); + + if (!((ctor->type == JSCTOR_JSON_PARSE || + ctor->type == JSCTOR_JSON_SCALAR) && + ctor->returning->typid == JSONOID)) + get_json_returning(ctor->returning, buf, true); +} + +static void +get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + const char *funcname; + int nargs; + ListCell *lc; + + switch (ctor->type) + { + case JSCTOR_JSON_PARSE: + funcname = "JSON"; + break; + case JSCTOR_JSON_SCALAR: + funcname = "JSON_SCALAR"; + break; + case JSCTOR_JSON_SERIALIZE: + funcname = "JSON_SERIALIZE"; + break; + case JSCTOR_JSON_OBJECT: + funcname = "JSON_OBJECT"; + break; + case JSCTOR_JSON_ARRAY: + funcname = "JSON_ARRAY"; + break; + case JSCTOR_JSON_OBJECTAGG: + get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true); + return; + case JSCTOR_JSON_ARRAYAGG: + get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false); + return; + default: + elog(ERROR, "invalid JsonConstructorExprType %d", ctor->type); + } + + appendStringInfo(buf, "%s(", funcname); + + nargs = 0; + foreach(lc, ctor->args) + { + if (nargs > 0) + { + const char *sep = ctor->type == JSCTOR_JSON_OBJECT && + (nargs % 2) != 0 ? " : " : ", "; + + appendStringInfoString(buf, sep); + } + + get_rule_expr((Node *) lfirst(lc), context, true); + + nargs++; + } + + get_json_constructor_options(ctor, buf); + + appendStringInfo(buf, ")"); +} + +/* + * get_proc_expr - Parse back a CallStmt node + */ +static void +get_proc_expr(CallStmt *stmt, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + Oid functionOid = stmt->funcexpr->funcid; + bool use_variadic; + Oid *argumentTypes; + List *finalArgumentList = NIL; + ListCell *argumentCell; + List *namedArgList = NIL; + int numberOfArgs = -1; + + if (!get_merged_argument_list(stmt, &namedArgList, &argumentTypes, + &finalArgumentList, &numberOfArgs)) + { + /* Nothing merged i.e. no OUT arguments */ + get_func_expr((FuncExpr *) stmt->funcexpr, context, showimplicit); + return; + } + + appendStringInfo(buf, "%s(", + generate_function_name(functionOid, numberOfArgs, + namedArgList, argumentTypes, + stmt->funcexpr->funcvariadic, + &use_variadic, + context->special_exprkind)); + int argNumber = 0; + foreach(argumentCell, finalArgumentList) + { + if (argNumber++ > 0) + appendStringInfoString(buf, ", "); + if (use_variadic && lnext(finalArgumentList, argumentCell) == NULL) + appendStringInfoString(buf, "VARIADIC "); + get_rule_expr((Node *) lfirst(argumentCell), context, true); + argNumber++; + } + + appendStringInfoChar(buf, ')'); +} + +/* + * get_agg_expr_helper - Parse back an Aggref node + */ +static void +get_agg_expr_helper(Aggref *aggref, deparse_context *context, + Aggref *original_aggref, const char *funcname, + const char *options, bool is_json_objectagg) +{ + StringInfo buf = context->buf; + Oid argtypes[FUNC_MAX_ARGS]; + int nargs; + bool use_variadic = false; + + /* + * 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); + + if (!funcname) + funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, + argtypes, aggref->aggvariadic, + &use_variadic, + context->special_exprkind); + + /* Print the aggregate name, schema-qualified if needed */ + appendStringInfo(buf, "%s(%s", funcname, + (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) + { + if (is_json_objectagg) + { + if (i > 2) + break; /* skip ABSENT ON NULL and WITH UNIQUE + * args */ + + appendStringInfoString(buf, " : "); + } + else + 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 (options) + appendStringInfoString(buf, options); + + if (aggref->aggfilter != NULL) + { + appendStringInfoString(buf, ") FILTER (WHERE "); + get_rule_expr((Node *) aggref->aggfilter, context, false); + } + + appendStringInfoChar(buf, ')'); +} + +/* + * get_agg_expr - Parse back an Aggref node + */ +static void +get_agg_expr(Aggref *aggref, deparse_context *context, Aggref *original_aggref) +{ + get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL, + false); +} + +/* + * 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_helper - Parse back a WindowFunc node + */ +static void +get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context, + const char *funcname, const char *options, + bool is_json_objectagg) +{ + 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++; + } + + if (!funcname) + funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, + argtypes, false, NULL, + context->special_exprkind); + + appendStringInfo(buf, "%s(", funcname); + + /* winstar can be set only in zero-argument aggregates */ + if (wfunc->winstar) + appendStringInfoChar(buf, '*'); + else + { + if (is_json_objectagg) + { + get_rule_expr((Node *) linitial(wfunc->args), context, false); + appendStringInfoString(buf, " : "); + get_rule_expr((Node *) lsecond(wfunc->args), context, false); + } + else + get_rule_expr((Node *) wfunc->args, context, true); + } + + if (options) + appendStringInfoString(buf, options); + + 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_windowfunc_expr - Parse back a WindowFunc node + */ +static void +get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +{ + get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false); +} + +/* + * get_func_sql_syntax - Parse back a SQL-syntax function call + * + * Returns true if we successfully deparsed, false if we did not + * recognize the function. + */ +static bool +get_func_sql_syntax(FuncExpr *expr, deparse_context *context) +{ + StringInfo buf = context->buf; + Oid funcoid = expr->funcid; + + switch (funcoid) + { + case F_TIMEZONE_INTERVAL_TIMESTAMP: + case F_TIMEZONE_INTERVAL_TIMESTAMPTZ: + case F_TIMEZONE_INTERVAL_TIMETZ: + case F_TIMEZONE_TEXT_TIMESTAMP: + case F_TIMEZONE_TEXT_TIMESTAMPTZ: + case F_TIMEZONE_TEXT_TIMETZ: + /* AT TIME ZONE ... note reversed argument order */ + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, " AT TIME ZONE "); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL: + case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ: + case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL: + case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ: + case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL: + case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP: + case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL: + case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP: + case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ: + case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL: + case F_OVERLAPS_TIME_INTERVAL_TIME_TIME: + case F_OVERLAPS_TIME_TIME_TIME_INTERVAL: + case F_OVERLAPS_TIME_TIME_TIME_TIME: + /* (x1, x2) OVERLAPS (y1, y2) */ + appendStringInfoString(buf, "(("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, ", "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, ") OVERLAPS ("); + get_rule_expr((Node *) lthird(expr->args), context, false); + appendStringInfoString(buf, ", "); + get_rule_expr((Node *) lfourth(expr->args), context, false); + appendStringInfoString(buf, "))"); + return true; + + case F_EXTRACT_TEXT_DATE: + case F_EXTRACT_TEXT_TIME: + case F_EXTRACT_TEXT_TIMETZ: + case F_EXTRACT_TEXT_TIMESTAMP: + case F_EXTRACT_TEXT_TIMESTAMPTZ: + case F_EXTRACT_TEXT_INTERVAL: + /* EXTRACT (x FROM y) */ + appendStringInfoString(buf, "EXTRACT("); + { + Const *con = (Const *) linitial(expr->args); + + Assert(IsA(con, Const) && + con->consttype == TEXTOID && + !con->constisnull); + appendStringInfoString(buf, TextDatumGetCString(con->constvalue)); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_IS_NORMALIZED: + /* IS xxx NORMALIZED */ + appendStringInfoString(buf, "(("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, ") IS"); + if (list_length(expr->args) == 2) + { + Const *con = (Const *) lsecond(expr->args); + + Assert(IsA(con, Const) && + con->consttype == TEXTOID && + !con->constisnull); + appendStringInfo(buf, " %s", + TextDatumGetCString(con->constvalue)); + } + appendStringInfoString(buf, " NORMALIZED)"); + return true; + + case F_PG_COLLATION_FOR: + /* COLLATION FOR */ + appendStringInfoString(buf, "COLLATION FOR ("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + /* + * XXX EXTRACT, a/k/a date_part(), is intentionally not covered + * yet. Add it after we change the return type to numeric. + */ + + case F_NORMALIZE: + /* NORMALIZE() */ + appendStringInfoString(buf, "NORMALIZE("); + get_rule_expr((Node *) linitial(expr->args), context, false); + if (list_length(expr->args) == 2) + { + Const *con = (Const *) lsecond(expr->args); + + Assert(IsA(con, Const) && + con->consttype == TEXTOID && + !con->constisnull); + appendStringInfo(buf, ", %s", + TextDatumGetCString(con->constvalue)); + } + appendStringInfoChar(buf, ')'); + return true; + + case F_OVERLAY_BIT_BIT_INT4: + case F_OVERLAY_BIT_BIT_INT4_INT4: + case F_OVERLAY_BYTEA_BYTEA_INT4: + case F_OVERLAY_BYTEA_BYTEA_INT4_INT4: + case F_OVERLAY_TEXT_TEXT_INT4: + case F_OVERLAY_TEXT_TEXT_INT4_INT4: + /* OVERLAY() */ + appendStringInfoString(buf, "OVERLAY("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, " PLACING "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) lthird(expr->args), context, false); + if (list_length(expr->args) == 4) + { + appendStringInfoString(buf, " FOR "); + get_rule_expr((Node *) lfourth(expr->args), context, false); + } + appendStringInfoChar(buf, ')'); + return true; + + case F_POSITION_BIT_BIT: + case F_POSITION_BYTEA_BYTEA: + case F_POSITION_TEXT_TEXT: + /* POSITION() ... extra parens since args are b_expr not a_expr */ + appendStringInfoString(buf, "POSITION(("); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, ") IN ("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, "))"); + return true; + + case F_SUBSTRING_BIT_INT4: + case F_SUBSTRING_BIT_INT4_INT4: + case F_SUBSTRING_BYTEA_INT4: + case F_SUBSTRING_BYTEA_INT4_INT4: + case F_SUBSTRING_TEXT_INT4: + case F_SUBSTRING_TEXT_INT4_INT4: + /* SUBSTRING FROM/FOR (i.e., integer-position variants) */ + appendStringInfoString(buf, "SUBSTRING("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + if (list_length(expr->args) == 3) + { + appendStringInfoString(buf, " FOR "); + get_rule_expr((Node *) lthird(expr->args), context, false); + } + appendStringInfoChar(buf, ')'); + return true; + + case F_SUBSTRING_TEXT_TEXT_TEXT: + /* SUBSTRING SIMILAR/ESCAPE */ + appendStringInfoString(buf, "SUBSTRING("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, " SIMILAR "); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, " ESCAPE "); + get_rule_expr((Node *) lthird(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_BTRIM_BYTEA_BYTEA: + case F_BTRIM_TEXT: + case F_BTRIM_TEXT_TEXT: + /* TRIM() */ + appendStringInfoString(buf, "TRIM(BOTH"); + if (list_length(expr->args) == 2) + { + appendStringInfoChar(buf, ' '); + get_rule_expr((Node *) lsecond(expr->args), context, false); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_LTRIM_BYTEA_BYTEA: + case F_LTRIM_TEXT: + case F_LTRIM_TEXT_TEXT: + /* TRIM() */ + appendStringInfoString(buf, "TRIM(LEADING"); + if (list_length(expr->args) == 2) + { + appendStringInfoChar(buf, ' '); + get_rule_expr((Node *) lsecond(expr->args), context, false); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_RTRIM_BYTEA_BYTEA: + case F_RTRIM_TEXT: + case F_RTRIM_TEXT_TEXT: + /* TRIM() */ + appendStringInfoString(buf, "TRIM(TRAILING"); + if (list_length(expr->args) == 2) + { + appendStringInfoChar(buf, ' '); + get_rule_expr((Node *) lsecond(expr->args), context, false); + } + appendStringInfoString(buf, " FROM "); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoChar(buf, ')'); + return true; + + case F_XMLEXISTS: + /* XMLEXISTS ... extra parens because args are c_expr */ + appendStringInfoString(buf, "XMLEXISTS(("); + get_rule_expr((Node *) linitial(expr->args), context, false); + appendStringInfoString(buf, ") PASSING ("); + get_rule_expr((Node *) lsecond(expr->args), context, false); + appendStringInfoString(buf, "))"); + return true; + } + return false; +} + +/* + * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node + */ +static void +get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context, + const char *funcname, bool is_json_objectagg) +{ + StringInfoData options; + + initStringInfo(&options); + get_json_constructor_options(ctor, &options); + + if (IsA(ctor->func, Aggref)) + get_agg_expr_helper((Aggref *) ctor->func, context, + (Aggref *) ctor->func, + funcname, options.data, is_json_objectagg); + else if (IsA(ctor->func, WindowFunc)) + get_windowfunc_expr_helper((WindowFunc *) ctor->func, context, + funcname, options.data, + is_json_objectagg); + else + elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d", + nodeTag(ctor->func)); +} + +/* ---------- + * 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, false, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + + if (need_paren) + appendStringInfoString(buf, "))"); + else + appendStringInfoChar(buf, ')'); +} + + +/* ---------- + * get_xmltable - Parse back a XMLTABLE function + * ---------- + */ +static void +get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit) +{ + StringInfo buf = context->buf; + + 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_json_nested_columns - Parse back nested JSON_TABLE columns + */ +static void +get_json_table_nested_columns(TableFunc *tf, Node *node, + deparse_context *context, bool showimplicit, + bool needcomma) +{ + if (IsA(node, JsonTableSibling)) + { + JsonTableSibling *n = (JsonTableSibling *) node; + + get_json_table_nested_columns(tf, n->larg, context, showimplicit, + needcomma); + get_json_table_nested_columns(tf, n->rarg, context, showimplicit, true); + } + else + { + JsonTableParent *n = castNode(JsonTableParent, node); + + if (needcomma) + appendStringInfoChar(context->buf, ','); + + appendStringInfoChar(context->buf, ' '); + appendContextKeyword(context, "NESTED PATH ", 0, 0, 0); + get_const_expr(n->path, context, -1); + appendStringInfo(context->buf, " AS %s", quote_identifier(n->name)); + get_json_table_columns(tf, n, context, showimplicit); + } +} + +/* + * get_json_table_plan - Parse back a JSON_TABLE plan + */ +static void +get_json_table_plan(TableFunc *tf, Node *node, deparse_context *context, + bool parenthesize) +{ + if (parenthesize) + appendStringInfoChar(context->buf, '('); + + if (IsA(node, JsonTableSibling)) + { + JsonTableSibling *n = (JsonTableSibling *) node; + + get_json_table_plan(tf, n->larg, context, + IsA(n->larg, JsonTableSibling) || + castNode(JsonTableParent, n->larg)->child); + + appendStringInfoString(context->buf, n->cross ? " CROSS " : " UNION "); + + get_json_table_plan(tf, n->rarg, context, + IsA(n->rarg, JsonTableSibling) || + castNode(JsonTableParent, n->rarg)->child); + } + else + { + JsonTableParent *n = castNode(JsonTableParent, node); + + appendStringInfoString(context->buf, quote_identifier(n->name)); + + if (n->child) + { + appendStringInfoString(context->buf, + n->outerJoin ? " OUTER " : " INNER "); + get_json_table_plan(tf, n->child, context, + IsA(n->child, JsonTableSibling)); + } + } + + if (parenthesize) + appendStringInfoChar(context->buf, ')'); +} + +/* + * get_json_table_columns - Parse back JSON_TABLE columns + */ +static void +get_json_table_columns(TableFunc *tf, JsonTableParent *node, + deparse_context *context, bool showimplicit) +{ + StringInfo buf = context->buf; + JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr); + ListCell *lc_colname; + ListCell *lc_coltype; + ListCell *lc_coltypmod; + ListCell *lc_colvarexpr; + int colnum = 0; + + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "COLUMNS (", 0, 0, 0); + + if (PRETTY_INDENT(context)) + context->indentLevel += PRETTYINDENT_VAR; + + forfour(lc_colname, tf->colnames, + lc_coltype, tf->coltypes, + lc_coltypmod, tf->coltypmods, + lc_colvarexpr, tf->colvalexprs) + { + char *colname = strVal(lfirst(lc_colname)); + JsonExpr *colexpr; + Oid typid; + int32 typmod; + bool ordinality; + JsonBehaviorType default_behavior; + + typid = lfirst_oid(lc_coltype); + typmod = lfirst_int(lc_coltypmod); + colexpr = castNode(JsonExpr, lfirst(lc_colvarexpr)); + + if (colnum < node->colMin) + { + colnum++; + continue; + } + + if (colnum > node->colMax) + break; + + if (colnum > node->colMin) + appendStringInfoString(buf, ", "); + + colnum++; + + ordinality = !colexpr; + + appendContextKeyword(context, "", 0, 0, 0); + + appendStringInfo(buf, "%s %s", quote_identifier(colname), + ordinality ? "FOR ORDINALITY" : + format_type_with_typemod(typid, typmod)); + if (ordinality) + continue; + + if (colexpr->op == JSON_EXISTS_OP) + { + appendStringInfoString(buf, " EXISTS"); + default_behavior = JSON_BEHAVIOR_FALSE; + } + else + { + if (colexpr->op == JSON_QUERY_OP) + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(typid, &typcategory, &typispreferred); + + if (typcategory == TYPCATEGORY_STRING) + appendStringInfoString(buf, + colexpr->format->format_type == JS_FORMAT_JSONB ? + " FORMAT JSONB" : " FORMAT JSON"); + } + + default_behavior = JSON_BEHAVIOR_NULL; + } + + if (jexpr->on_error->btype == JSON_BEHAVIOR_ERROR) + default_behavior = JSON_BEHAVIOR_ERROR; + + appendStringInfoString(buf, " PATH "); + + get_json_path_spec(colexpr->path_spec, context, showimplicit); + + get_json_expr_options(colexpr, context, default_behavior); + } + + if (node->child) + get_json_table_nested_columns(tf, node->child, context, showimplicit, + node->colMax >= node->colMin); + + if (PRETTY_INDENT(context)) + context->indentLevel -= PRETTYINDENT_VAR; + + appendContextKeyword(context, ")", 0, 0, 0); +} + +/* ---------- + * get_json_table - Parse back a JSON_TABLE function + * ---------- + */ +static void +get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit) +{ + StringInfo buf = context->buf; + JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr); + JsonTableParent *root = castNode(JsonTableParent, tf->plan); + + appendStringInfoString(buf, "JSON_TABLE("); + + if (PRETTY_INDENT(context)) + context->indentLevel += PRETTYINDENT_VAR; + + appendContextKeyword(context, "", 0, 0, 0); + + get_rule_expr(jexpr->formatted_expr, context, showimplicit); + + appendStringInfoString(buf, ", "); + + get_const_expr(root->path, context, -1); + + appendStringInfo(buf, " AS %s", quote_identifier(root->name)); + + if (jexpr->passing_values) + { + ListCell *lc1, + *lc2; + bool needcomma = false; + + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "PASSING ", 0, 0, 0); + + if (PRETTY_INDENT(context)) + context->indentLevel += PRETTYINDENT_VAR; + + forboth(lc1, jexpr->passing_names, + lc2, jexpr->passing_values) + { + if (needcomma) + appendStringInfoString(buf, ", "); + needcomma = true; + + appendContextKeyword(context, "", 0, 0, 0); + + get_rule_expr((Node *) lfirst(lc2), context, false); + appendStringInfo(buf, " AS %s", + quote_identifier((lfirst_node(String, lc1))->sval) + ); + } + + if (PRETTY_INDENT(context)) + context->indentLevel -= PRETTYINDENT_VAR; + } + + get_json_table_columns(tf, root, context, showimplicit); + + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "PLAN ", 0, 0, 0); + get_json_table_plan(tf, (Node *) root, context, true); + + if (jexpr->on_error->btype != JSON_BEHAVIOR_EMPTY) + get_json_behavior(jexpr->on_error, context, "ERROR"); + + if (PRETTY_INDENT(context)) + context->indentLevel -= PRETTYINDENT_VAR; + + appendContextKeyword(context, ")", 0, 0, 0); +} + +/* ---------- + * get_tablefunc - Parse back a table function + * ---------- + */ +static void +get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) +{ + /* XMLTABLE and JSON_TABLE are the only existing implementations. */ + + if (tf->functype == TFT_XMLTABLE) + get_xmltable(tf, context, showimplicit); + else if (tf->functype == TFT_JSON_TABLE) + get_json_table(tf, context, showimplicit); +} + +/* ---------- + * 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, + true, + 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_UNNEST_ANYARRAY || + 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) + { + /* 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, ')'); + + if (j->join_using_alias) + appendStringInfo(buf, " AS %s", + quote_identifier(j->join_using_alias->aliasname)); + } + 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_or_temp(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_or_temp(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_or_temp(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, false, + &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_or_temp(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_or_temp(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; + + appendStringInfoChar(buf, '('); + sep = ""; + foreach(cell, bound_datums) + { + PartitionRangeDatum *datum = + lfirst_node(PartitionRangeDatum, 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_15) && (PG_VERSION_NUM < PG_VERSION_16) */ diff --git a/src/backend/distributed/executor/local_executor.c b/src/backend/distributed/executor/local_executor.c index d0f5b1fa9..ffd063ca0 100644 --- a/src/backend/distributed/executor/local_executor.c +++ b/src/backend/distributed/executor/local_executor.c @@ -519,7 +519,7 @@ LogLocalCommand(Task *task) } ereport(NOTICE, (errmsg("executing the command locally: %s", - ApplyLogRedaction(command)))); + command))); } diff --git a/src/backend/distributed/executor/multi_client_executor.c b/src/backend/distributed/executor/multi_client_executor.c index 64edb2014..ddcdc9a6e 100644 --- a/src/backend/distributed/executor/multi_client_executor.c +++ b/src/backend/distributed/executor/multi_client_executor.c @@ -155,9 +155,9 @@ MultiClientSendQuery(int32 connectionId, const char *query) * we cannot send the queries that Citus itself produced. */ ereport(WARNING, (errmsg("could not send remote query \"%s\"", - ApplyLogRedaction(query)), + query), errdetail("Client error: %s", - ApplyLogRedaction(errorMessage)))); + errorMessage))); success = false; } diff --git a/src/backend/distributed/metadata/metadata_cache.c b/src/backend/distributed/metadata/metadata_cache.c index 949fe91c1..c22700ccf 100644 --- a/src/backend/distributed/metadata/metadata_cache.c +++ b/src/backend/distributed/metadata/metadata_cache.c @@ -495,6 +495,49 @@ IsCitusTableTypeInternal(char partitionMethod, char replicationModel, } +/* + * GetTableTypeName returns string representation of the table type. + */ +char * +GetTableTypeName(Oid tableId) +{ + bool regularTable = false; + char partitionMethod = ' '; + char replicationModel = ' '; + if (IsCitusTable(tableId)) + { + CitusTableCacheEntry *referencingCacheEntry = GetCitusTableCacheEntry(tableId); + partitionMethod = referencingCacheEntry->partitionMethod; + replicationModel = referencingCacheEntry->replicationModel; + } + else + { + regularTable = true; + } + + if (regularTable) + { + return "regular table"; + } + else if (partitionMethod == 'h') + { + return "distributed table"; + } + else if (partitionMethod == 'n' && replicationModel == 't') + { + return "reference table"; + } + else if (partitionMethod == 'n' && replicationModel != 't') + { + return "citus local table"; + } + else + { + return "unknown table"; + } +} + + /* * IsCitusTable returns whether relationId is a distributed relation or * not. @@ -617,6 +660,45 @@ PartitionColumnViaCatalog(Oid relationId) } +/* + * ColocationIdViaCatalog gets a relationId and returns the colocation + * id column from pg_dist_partition via reading from catalog. + */ +uint32 +ColocationIdViaCatalog(Oid relationId) +{ + HeapTuple partitionTuple = PgDistPartitionTupleViaCatalog(relationId); + if (!HeapTupleIsValid(partitionTuple)) + { + return INVALID_COLOCATION_ID; + } + + Datum datumArray[Natts_pg_dist_partition]; + bool isNullArray[Natts_pg_dist_partition]; + + Relation pgDistPartition = table_open(DistPartitionRelationId(), AccessShareLock); + + TupleDesc tupleDescriptor = RelationGetDescr(pgDistPartition); + heap_deform_tuple(partitionTuple, tupleDescriptor, datumArray, isNullArray); + + if (isNullArray[Anum_pg_dist_partition_colocationid - 1]) + { + /* colocation id cannot be NULL, still let's make sure */ + heap_freetuple(partitionTuple); + table_close(pgDistPartition, NoLock); + return INVALID_COLOCATION_ID; + } + + Datum colocationIdDatum = datumArray[Anum_pg_dist_partition_colocationid - 1]; + uint32 colocationId = DatumGetUInt32(colocationIdDatum); + + heap_freetuple(partitionTuple); + table_close(pgDistPartition, NoLock); + + return colocationId; +} + + /* * PgDistPartitionTupleViaCatalog is a helper function that searches * pg_dist_partition for the given relationId. The caller is responsible @@ -2831,42 +2913,6 @@ TextOutFunctionId(void) } -/* - * PgTableVisibleFuncId returns oid of the pg_table_is_visible function. - */ -Oid -PgTableVisibleFuncId(void) -{ - if (MetadataCache.pgTableIsVisibleFuncId == InvalidOid) - { - const int argCount = 1; - - MetadataCache.pgTableIsVisibleFuncId = - FunctionOid("pg_catalog", "pg_table_is_visible", argCount); - } - - return MetadataCache.pgTableIsVisibleFuncId; -} - - -/* - * CitusTableVisibleFuncId returns oid of the citus_table_is_visible function. - */ -Oid -CitusTableVisibleFuncId(void) -{ - if (MetadataCache.citusTableIsVisibleFuncId == InvalidOid) - { - const int argCount = 1; - - MetadataCache.citusTableIsVisibleFuncId = - FunctionOid("pg_catalog", "citus_table_is_visible", argCount); - } - - return MetadataCache.citusTableIsVisibleFuncId; -} - - /* * RelationIsAKnownShardFuncId returns oid of the relation_is_a_known_shard function. */ @@ -4424,17 +4470,6 @@ CitusTableTypeIdList(CitusTableType citusTableType) } -/* - * ClusterHasReferenceTable returns true if the cluster has - * any reference table. - */ -bool -ClusterHasReferenceTable(void) -{ - return list_length(CitusTableTypeIdList(REFERENCE_TABLE)) > 0; -} - - /* * InvalidateNodeRelationCacheCallback destroys the WorkerNodeHash when * any change happens on pg_dist_node table. It also set WorkerNodeHash to diff --git a/src/backend/distributed/metadata/metadata_sync.c b/src/backend/distributed/metadata/metadata_sync.c index f7ab4376e..44c5cded2 100644 --- a/src/backend/distributed/metadata/metadata_sync.c +++ b/src/backend/distributed/metadata/metadata_sync.c @@ -159,6 +159,7 @@ PG_FUNCTION_INFO_V1(worker_record_sequence_dependency); * or regular users as long as the regular user owns the input object. */ PG_FUNCTION_INFO_V1(citus_internal_add_partition_metadata); +PG_FUNCTION_INFO_V1(citus_internal_delete_partition_metadata); PG_FUNCTION_INFO_V1(citus_internal_add_shard_metadata); PG_FUNCTION_INFO_V1(citus_internal_add_placement_metadata); PG_FUNCTION_INFO_V1(citus_internal_update_placement_metadata); @@ -1211,6 +1212,24 @@ DistributionDeleteCommand(const char *schemaName, const char *tableName) } +/* + * DistributionDeleteMetadataCommand returns a query to delete pg_dist_partition + * metadata from a worker node for a given table. + */ +char * +DistributionDeleteMetadataCommand(Oid relationId) +{ + StringInfo deleteCommand = makeStringInfo(); + char *qualifiedRelationName = generate_qualified_relation_name(relationId); + + appendStringInfo(deleteCommand, + "SELECT pg_catalog.citus_internal_delete_partition_metadata(%s)", + quote_literal_cstr(qualifiedRelationName)); + + return deleteCommand->data; +} + + /* * TableOwnerResetCommand generates a commands that can be executed * to reset the table owner. @@ -3199,6 +3218,35 @@ EnsurePartitionMetadataIsSane(Oid relationId, char distributionMethod, int coloc } +/* + * citus_internal_delete_partition_metadata is an internal UDF to + * delete a row in pg_dist_partition. + */ +Datum +citus_internal_delete_partition_metadata(PG_FUNCTION_ARGS) +{ + CheckCitusVersion(ERROR); + + PG_ENSURE_ARGNOTNULL(0, "relation"); + Oid relationId = PG_GETARG_OID(0); + + /* only owner of the table (or superuser) is allowed to add the Citus metadata */ + EnsureTableOwner(relationId); + + /* we want to serialize all the metadata changes to this table */ + LockRelationOid(relationId, ShareUpdateExclusiveLock); + + if (!ShouldSkipMetadataChecks()) + { + EnsureCoordinatorInitiatedOperation(); + } + + DeletePartitionRow(relationId); + + PG_RETURN_VOID(); +} + + /* * citus_internal_add_shard_metadata is an internal UDF to * add a row to pg_dist_shard. diff --git a/src/backend/distributed/metadata/metadata_utility.c b/src/backend/distributed/metadata/metadata_utility.c index ff85f6930..ee7be267e 100644 --- a/src/backend/distributed/metadata/metadata_utility.c +++ b/src/backend/distributed/metadata/metadata_utility.c @@ -39,6 +39,7 @@ #include "distributed/metadata_utility.h" #include "distributed/coordinator_protocol.h" #include "distributed/metadata_cache.h" +#include "distributed/metadata_sync.h" #include "distributed/multi_join_order.h" #include "distributed/multi_logical_optimizer.h" #include "distributed/multi_partitioning_utils.h" @@ -81,6 +82,8 @@ static bool DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId, uint64 *tableSize); static List * ShardIntervalsOnWorkerGroup(WorkerNode *workerNode, Oid relationId); static char * GenerateShardStatisticsQueryForShardList(List *shardIntervalList); +static char * GenerateSizeQueryForRelationNameList(List *quotedShardNames, + char *sizeFunction); static char * GetWorkerPartitionedSizeUDFNameBySizeQueryType(SizeQueryType sizeQueryType); static char * GetSizeQueryBySizeQueryType(SizeQueryType sizeQueryType); static char * GenerateAllShardStatisticsQueryForNode(WorkerNode *workerNode, @@ -720,7 +723,8 @@ GenerateSizeQueryOnMultiplePlacements(List *shardIntervalList, { StringInfo selectQuery = makeStringInfo(); - appendStringInfo(selectQuery, "SELECT "); + List *partitionedShardNames = NIL; + List *nonPartitionedShardNames = NIL; ShardInterval *shardInterval = NULL; foreach_ptr(shardInterval, shardIntervalList) @@ -746,30 +750,76 @@ GenerateSizeQueryOnMultiplePlacements(List *shardIntervalList, char *shardQualifiedName = quote_qualified_identifier(schemaName, shardName); char *quotedShardName = quote_literal_cstr(shardQualifiedName); + /* for partitoned tables, we will call worker_partitioned_... size functions */ if (optimizePartitionCalculations && PartitionedTable(shardInterval->relationId)) { - appendStringInfo(selectQuery, GetWorkerPartitionedSizeUDFNameBySizeQueryType( - sizeQueryType), quotedShardName); + partitionedShardNames = lappend(partitionedShardNames, quotedShardName); } + + /* for non-partitioned tables, we will use Postgres' size functions */ else { - appendStringInfo(selectQuery, GetSizeQueryBySizeQueryType(sizeQueryType), - quotedShardName); + nonPartitionedShardNames = lappend(nonPartitionedShardNames, quotedShardName); } - - appendStringInfo(selectQuery, " + "); } - /* - * Add 0 as a last size, it handles empty list case and makes size control checks - * unnecessary which would have implemented without this line. - */ - appendStringInfo(selectQuery, "0;"); + /* SELECT SUM(worker_partitioned_...) FROM VALUES (...) */ + char *subqueryForPartitionedShards = + GenerateSizeQueryForRelationNameList(partitionedShardNames, + GetWorkerPartitionedSizeUDFNameBySizeQueryType( + sizeQueryType)); + + /* SELECT SUM(pg_..._size) FROM VALUES (...) */ + char *subqueryForNonPartitionedShards = + GenerateSizeQueryForRelationNameList(nonPartitionedShardNames, + GetSizeQueryBySizeQueryType(sizeQueryType)); + + appendStringInfo(selectQuery, "SELECT (%s) + (%s);", + subqueryForPartitionedShards, subqueryForNonPartitionedShards); + + elog(DEBUG4, "Size Query: %s", selectQuery->data); return selectQuery; } +/* + * GenerateSizeQueryForPartitionedShards generates and returns a query with a template: + * SELECT SUM( (relid) ) FROM (VALUES (), (), ...) as q(relid) + */ +static char * +GenerateSizeQueryForRelationNameList(List *quotedShardNames, char *sizeFunction) +{ + if (list_length(quotedShardNames) == 0) + { + return "SELECT 0"; + } + + StringInfo selectQuery = makeStringInfo(); + + appendStringInfo(selectQuery, "SELECT SUM("); + appendStringInfo(selectQuery, sizeFunction, "relid"); + appendStringInfo(selectQuery, ") FROM (VALUES "); + + bool addComma = false; + char *quotedShardName = NULL; + foreach_ptr(quotedShardName, quotedShardNames) + { + if (addComma) + { + appendStringInfoString(selectQuery, ", "); + } + addComma = true; + + appendStringInfo(selectQuery, "(%s)", quotedShardName); + } + + appendStringInfoString(selectQuery, ") as q(relid)"); + + return selectQuery->data; +} + + /* * GetWorkerPartitionedSizeUDFNameBySizeQueryType returns the corresponding worker * partitioned size query for given query type. @@ -2104,6 +2154,96 @@ UpdatePgDistPartitionAutoConverted(Oid citusTableId, bool autoConverted) } +/* + * UpdateDistributionColumnGlobally sets the distribution column and colocation ID + * for a table in pg_dist_partition on all nodes + */ +void +UpdateDistributionColumnGlobally(Oid relationId, char distributionMethod, + Var *distributionColumn, int colocationId) +{ + UpdateDistributionColumn(relationId, distributionMethod, distributionColumn, + colocationId); + + if (ShouldSyncTableMetadata(relationId)) + { + /* we use delete+insert because syncing uses specialized RPCs */ + char *deleteMetadataCommand = DistributionDeleteMetadataCommand(relationId); + SendCommandToWorkersWithMetadata(deleteMetadataCommand); + + /* pick up the new metadata (updated above) */ + CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId); + char *insertMetadataCommand = DistributionCreateCommand(cacheEntry); + SendCommandToWorkersWithMetadata(insertMetadataCommand); + } +} + + +/* + * UpdateDistributionColumn sets the distribution column and colocation ID for a table + * in pg_dist_partition. + */ +void +UpdateDistributionColumn(Oid relationId, char distributionMethod, Var *distributionColumn, + int colocationId) +{ + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + bool indexOK = true; + Datum values[Natts_pg_dist_partition]; + bool isnull[Natts_pg_dist_partition]; + bool replace[Natts_pg_dist_partition]; + + Relation pgDistPartition = table_open(DistPartitionRelationId(), RowExclusiveLock); + TupleDesc tupleDescriptor = RelationGetDescr(pgDistPartition); + ScanKeyInit(&scanKey[0], Anum_pg_dist_partition_logicalrelid, + BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId)); + + SysScanDesc scanDescriptor = systable_beginscan(pgDistPartition, + DistPartitionLogicalRelidIndexId(), + indexOK, + NULL, scanKeyCount, scanKey); + + HeapTuple heapTuple = systable_getnext(scanDescriptor); + if (!HeapTupleIsValid(heapTuple)) + { + ereport(ERROR, (errmsg("could not find valid entry for citus table with oid: %u", + relationId))); + } + + memset(replace, 0, sizeof(replace)); + + replace[Anum_pg_dist_partition_partmethod - 1] = true; + values[Anum_pg_dist_partition_partmethod - 1] = CharGetDatum(distributionMethod); + isnull[Anum_pg_dist_partition_partmethod - 1] = false; + + replace[Anum_pg_dist_partition_colocationid - 1] = true; + values[Anum_pg_dist_partition_colocationid - 1] = UInt32GetDatum(colocationId); + isnull[Anum_pg_dist_partition_colocationid - 1] = false; + + replace[Anum_pg_dist_partition_autoconverted - 1] = true; + values[Anum_pg_dist_partition_autoconverted - 1] = BoolGetDatum(false); + isnull[Anum_pg_dist_partition_autoconverted - 1] = false; + + char *distributionColumnString = nodeToString((Node *) distributionColumn); + + replace[Anum_pg_dist_partition_partkey - 1] = true; + values[Anum_pg_dist_partition_partkey - 1] = + CStringGetTextDatum(distributionColumnString); + isnull[Anum_pg_dist_partition_partkey - 1] = false; + + heapTuple = heap_modify_tuple(heapTuple, tupleDescriptor, values, isnull, replace); + + CatalogTupleUpdate(pgDistPartition, &heapTuple->t_self, heapTuple); + + CitusInvalidateRelcacheByRelid(relationId); + CommandCounterIncrement(); + + systable_endscan(scanDescriptor); + table_close(pgDistPartition, NoLock); +} + + /* * Check that the current user has `mode` permissions on relationId, error out * if not. Superusers always have such permissions. @@ -2135,21 +2275,6 @@ EnsureTableOwner(Oid relationId) } -/* - * Check that the current user has owner rights to the schema, 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 diff --git a/src/backend/distributed/metadata/node_metadata.c b/src/backend/distributed/metadata/node_metadata.c index 5603189c5..3f729eccf 100644 --- a/src/backend/distributed/metadata/node_metadata.c +++ b/src/backend/distributed/metadata/node_metadata.c @@ -2546,6 +2546,24 @@ EnsureCoordinator(void) } +/* + * EnsureCoordinatorIsInMetadata checks whether the coordinator is added to the + * metadata, which is required for many operations. + */ +void +EnsureCoordinatorIsInMetadata(void) +{ + bool isCoordinatorInMetadata = false; + PrimaryNodeForGroup(COORDINATOR_GROUP_ID, &isCoordinatorInMetadata); + if (!isCoordinatorInMetadata) + { + ereport(ERROR, (errmsg("coordinator is not added to the metadata"), + errhint("Use SELECT citus_set_coordinator_host('') " + "to configure the coordinator hostname"))); + } +} + + /* * InsertCoordinatorIfClusterEmpty can be used to ensure Citus tables can be * created even on a node that has just performed CREATE EXTENSION citus; diff --git a/src/backend/distributed/operations/citus_split_shard_by_split_points.c b/src/backend/distributed/operations/citus_split_shard_by_split_points.c index 28093f336..5bdbaf576 100644 --- a/src/backend/distributed/operations/citus_split_shard_by_split_points.c +++ b/src/backend/distributed/operations/citus_split_shard_by_split_points.c @@ -23,6 +23,7 @@ #include "distributed/connection_management.h" #include "distributed/remote_commands.h" #include "distributed/shard_split.h" +#include "distributed/utils/distribution_column_map.h" /* declarations for dynamic loading */ PG_FUNCTION_INFO_V1(citus_split_shard_by_split_points); @@ -52,12 +53,17 @@ citus_split_shard_by_split_points(PG_FUNCTION_ARGS) Oid shardTransferModeOid = PG_GETARG_OID(3); SplitMode shardSplitMode = LookupSplitMode(shardTransferModeOid); + DistributionColumnMap *distributionColumnOverrides = NULL; + List *sourceColocatedShardIntervalList = NIL; SplitShard( shardSplitMode, SHARD_SPLIT_API, shardIdToSplit, shardSplitPointsList, - nodeIdsForPlacementList); + nodeIdsForPlacementList, + distributionColumnOverrides, + sourceColocatedShardIntervalList, + INVALID_COLOCATION_ID); PG_RETURN_VOID(); } diff --git a/src/backend/distributed/operations/isolate_shards.c b/src/backend/distributed/operations/isolate_shards.c index d37ff3bee..c0f7739b8 100644 --- a/src/backend/distributed/operations/isolate_shards.c +++ b/src/backend/distributed/operations/isolate_shards.c @@ -33,6 +33,7 @@ #include "distributed/worker_transaction.h" #include "distributed/version_compat.h" #include "distributed/shard_split.h" +#include "distributed/utils/distribution_column_map.h" #include "nodes/pg_list.h" #include "storage/lock.h" #include "utils/builtins.h" @@ -163,12 +164,17 @@ isolate_tenant_to_new_shard(PG_FUNCTION_ARGS) nodeIdsForPlacementList = lappend_int(nodeIdsForPlacementList, sourceNodeId); } + DistributionColumnMap *distributionColumnOverrides = NULL; + List *sourceColocatedShardIntervalList = NIL; SplitMode splitMode = LookupSplitMode(shardTransferModeOid); SplitShard(splitMode, ISOLATE_TENANT_TO_NEW_SHARD, sourceShard->shardId, shardSplitPointsList, - nodeIdsForPlacementList); + nodeIdsForPlacementList, + distributionColumnOverrides, + sourceColocatedShardIntervalList, + INVALID_COLOCATION_ID); cacheEntry = GetCitusTableCacheEntry(relationId); ShardInterval *newShard = FindShardInterval(tenantIdDatum, cacheEntry); diff --git a/src/backend/distributed/operations/modify_multiple_shards.c b/src/backend/distributed/operations/modify_multiple_shards.c index ba87108aa..5ed9a5d35 100644 --- a/src/backend/distributed/operations/modify_multiple_shards.c +++ b/src/backend/distributed/operations/modify_multiple_shards.c @@ -78,7 +78,7 @@ master_modify_multiple_shards(PG_FUNCTION_ARGS) if (!IsA(queryTreeNode, DeleteStmt) && !IsA(queryTreeNode, UpdateStmt)) { ereport(ERROR, (errmsg("query \"%s\" is not a delete or update " - "statement", ApplyLogRedaction(queryString)))); + "statement", queryString))); } ereport(WARNING, (errmsg("master_modify_multiple_shards is deprecated and will be " diff --git a/src/backend/distributed/operations/repair_shards.c b/src/backend/distributed/operations/repair_shards.c index a4457d691..193797384 100644 --- a/src/backend/distributed/operations/repair_shards.c +++ b/src/backend/distributed/operations/repair_shards.c @@ -71,7 +71,6 @@ typedef struct ShardCommandList } ShardCommandList; /* local function forward declarations */ -static bool RelationCanPublishAllModifications(Oid relationId); static bool CanUseLogicalReplication(Oid relationId, char shardReplicationMode); static void ErrorIfTableCannotBeReplicated(Oid relationId); static void ErrorIfTargetNodeIsNotSafeToCopyTo(const char *targetNodeName, @@ -635,7 +634,7 @@ VerifyTablesHaveReplicaIdentity(List *colocatedTableList) * RelationCanPublishAllModifications returns true if the relation is safe to publish * all modification while being replicated via logical replication. */ -static bool +bool RelationCanPublishAllModifications(Oid relationId) { Relation relation = RelationIdGetRelation(relationId); diff --git a/src/backend/distributed/operations/shard_split.c b/src/backend/distributed/operations/shard_split.c index 9cdd3e7f7..bc4ad208b 100644 --- a/src/backend/distributed/operations/shard_split.c +++ b/src/backend/distributed/operations/shard_split.c @@ -30,23 +30,21 @@ #include "distributed/shard_split.h" #include "distributed/reference_table_utils.h" #include "distributed/repair_shards.h" +#include "distributed/resource_lock.h" #include "distributed/multi_partitioning_utils.h" #include "distributed/worker_manager.h" #include "distributed/worker_transaction.h" -#include "distributed/shard_cleaner.h" #include "distributed/shared_library_init.h" #include "distributed/pg_dist_shard.h" #include "distributed/metadata_sync.h" #include "distributed/multi_physical_planner.h" +#include "distributed/utils/distribution_column_map.h" #include "commands/dbcommands.h" #include "distributed/shardsplit_logical_replication.h" #include "distributed/deparse_shard_query.h" #include "distributed/shard_rebalancer.h" #include "postmaster/postmaster.h" -/* declarations for dynamic loading */ -bool DeferShardDeleteOnSplit = true; - /* * Entry for map that tracks ShardInterval -> Placement Node * created by split workflow. @@ -75,15 +73,12 @@ static void ErrorIfCannotSplitShardExtended(SplitOperation splitOperation, ShardInterval *shardIntervalToSplit, List *shardSplitPointsList, List *nodeIdsForPlacementList); -static void CreateAndCopySplitShardsForShardGroup(WorkerNode *sourceShardNode, - List *sourceColocatedShardIntervalList, - List *shardGroupSplitIntervalListList, - List *workersForPlacementList); -static bool CheckIfRelationWithSameNameExists(ShardInterval *shardInterval, - WorkerNode *workerNode); -static void CreateSplitShardsForShardGroup(List *shardGroupSplitIntervalListList, +static void ErrorIfModificationAndSplitInTheSameTransaction(SplitOperation + splitOperation); +static void CreateSplitShardsForShardGroup(HTAB *mapOfShardToPlacementCreatedByWorkflow, + List *shardGroupSplitIntervalListList, List *workersForPlacementList); -static void CreateDummyShardsForShardGroup(HTAB *mapOfPlacementToDummyShardList, +static void CreateDummyShardsForShardGroup(HTAB *mapOfDummyShardToPlacement, List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, WorkerNode *sourceWorkerNode, @@ -92,7 +87,7 @@ static HTAB * CreateWorkerForPlacementSet(List *workersForPlacementList); static void CreateAuxiliaryStructuresForShardGroup(List *shardGroupSplitIntervalListList, List *workersForPlacementList, bool includeReplicaIdentity); -static void CreateReplicaIdentitiesForDummyShards(HTAB *mapOfPlacementToDummyShardList); +static void CreateReplicaIdentitiesForDummyShards(HTAB *mapOfDummyShardToPlacement); static void CreateObjectOnPlacement(List *objectCreationCommandList, WorkerNode *workerNode); static List * CreateSplitIntervalsForShardGroup(List *sourceColocatedShardList, @@ -101,54 +96,78 @@ static void CreateSplitIntervalsForShard(ShardInterval *sourceShard, List *splitPointsForShard, List **shardSplitChildrenIntervalList); static void BlockingShardSplit(SplitOperation splitOperation, - ShardInterval *shardIntervalToSplit, + uint64 splitWorkflowId, + List *sourceColocatedShardIntervalList, List *shardSplitPointsList, - List *workersForPlacementList); + List *workersForPlacementList, + DistributionColumnMap *distributionColumnOverrides); static void NonBlockingShardSplit(SplitOperation splitOperation, - ShardInterval *shardIntervalToSplit, + uint64 splitWorkflowId, + List *sourceColocatedShardIntervalList, List *shardSplitPointsList, - List *workersForPlacementList); + List *workersForPlacementList, + DistributionColumnMap *distributionColumnOverrides, + uint32 targetColocationId); static void DoSplitCopy(WorkerNode *sourceShardNode, List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, List *workersForPlacementList, - char *snapShotName); + char *snapShotName, + DistributionColumnMap *distributionColumnOverrides); static StringInfo CreateSplitCopyCommand(ShardInterval *sourceShardSplitInterval, + char *distributionColumnName, List *splitChildrenShardIntervalList, List *workersForPlacementList); static Task * CreateSplitCopyTask(StringInfo splitCopyUdfCommand, char *snapshotName, int taskId, uint64 jobId); +static void UpdateDistributionColumnsForShardGroup(List *colocatedShardList, + DistributionColumnMap *distCols, + char distributionMethod, + int shardCount, + uint32 colocationId); static void InsertSplitChildrenShardMetadata(List *shardGroupSplitIntervalListList, List *workersForPlacementList); static void CreatePartitioningHierarchy(List *shardGroupSplitIntervalListList, List *workersForPlacementList); static void CreateForeignKeyConstraints(List *shardGroupSplitIntervalListList, List *workersForPlacementList); +static void TryDropSplitShardsOnFailure(HTAB *mapOfShardToPlacementCreatedByWorkflow); +static HTAB * CreateEmptyMapForShardsCreatedByWorkflow(); static Task * CreateTaskForDDLCommandList(List *ddlCommandList, WorkerNode *workerNode); static StringInfo CreateSplitShardReplicationSetupUDF( List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, - List *destinationWorkerNodesList); + List *destinationWorkerNodesList, + DistributionColumnMap * + distributionColumnOverrides); static List * ParseReplicationSlotInfoFromResult(PGresult *result); static List * ExecuteSplitShardReplicationSetupUDF(WorkerNode *sourceWorkerNode, List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, - List *destinationWorkerNodesList); -static void AddDummyShardEntryInMap(HTAB *mapOfPlacementToDummyShardList, uint32 - targetNodeId, + List *destinationWorkerNodesList, + DistributionColumnMap * + distributionColumnOverrides); +static void ExecuteSplitShardReleaseSharedMemory(WorkerNode *sourceWorkerNode); +static void AddDummyShardEntryInMap(HTAB *mapOfDummyShards, uint32 targetNodeId, ShardInterval *shardInterval); +static void DropDummyShards(HTAB *mapOfDummyShardToPlacement); +static void DropDummyShard(MultiConnection *connection, ShardInterval *shardInterval); static uint64 GetNextShardIdForSplitChild(void); +static void AcquireNonblockingSplitLock(Oid relationId); +static List * GetWorkerNodesFromWorkerIds(List *nodeIdsForPlacementList); /* Customize error message strings based on operation type */ static const char *const SplitOperationName[] = { [SHARD_SPLIT_API] = "split", [ISOLATE_TENANT_TO_NEW_SHARD] = "isolate", + [CREATE_DISTRIBUTED_TABLE] = "create" }; static const char *const SplitTargetName[] = { [SHARD_SPLIT_API] = "shard", [ISOLATE_TENANT_TO_NEW_SHARD] = "tenant", + [CREATE_DISTRIBUTED_TABLE] = "distributed table" }; /* Function definitions */ @@ -229,6 +248,12 @@ ErrorIfCannotSplitShardExtended(SplitOperation splitOperation, List *shardSplitPointsList, List *nodeIdsForPlacementList) { + /* we should not perform checks for create distributed table operation */ + if (splitOperation == CREATE_DISTRIBUTED_TABLE) + { + return; + } + CitusTableCacheEntry *cachedTableEntry = GetCitusTableCacheEntry( shardIntervalToSplit->relationId); @@ -351,20 +376,11 @@ ErrorIfCannotSplitShardExtended(SplitOperation splitOperation, /* - * SplitShard API to split a given shard (or shard group) based on specified split points - * to a set of destination nodes. - * 'splitMode' : Mode of split operation. - * 'splitOperation' : Customer operation that triggered split. - * 'shardInterval' : Source shard interval to be split. - * 'shardSplitPointsList' : Split Points list for the source 'shardInterval'. - * 'nodeIdsForPlacementList' : Placement list corresponding to split children. + * ErrorIfModificationAndSplitInTheSameTransaction will error if we detect split operation + * in the same transaction which has modification before. */ -void -SplitShard(SplitMode splitMode, - SplitOperation splitOperation, - uint64 shardIdToSplit, - List *shardSplitPointsList, - List *nodeIdsForPlacementList) +static void +ErrorIfModificationAndSplitInTheSameTransaction(SplitOperation splitOperation) { if (XactModificationLevel > XACT_MODIFICATION_NONE) { @@ -374,13 +390,82 @@ SplitShard(SplitMode splitMode, SplitOperationName[splitOperation], SplitTargetName[splitOperation]))); } - else if (PlacementMovedUsingLogicalReplicationInTX) +} + + +/* + * ErrorIfMultipleNonblockingMoveSplitInTheSameTransaction will error if we detect multiple + * nonblocking shard movements/splits in the same transaction. + */ +void +ErrorIfMultipleNonblockingMoveSplitInTheSameTransaction(void) +{ + if (PlacementMovedUsingLogicalReplicationInTX) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("multiple shard movements/splits via logical " "replication in the same transaction is currently " "not supported"))); } +} + + +/* + * GetWorkerNodesFromWorkerIds returns list of worker nodes given a list + * of worker ids. It will error if any node id is invalid. + */ +static List * +GetWorkerNodesFromWorkerIds(List *nodeIdsForPlacementList) +{ + List *workersForPlacementList = NIL; + int32 nodeId; + foreach_int(nodeId, nodeIdsForPlacementList) + { + uint32 nodeIdValue = (uint32) nodeId; + WorkerNode *workerNode = LookupNodeByNodeId(nodeIdValue); + + /* NodeId in Citus are unsigned and range from [1, 4294967296]. */ + if (nodeIdValue < 1 || workerNode == NULL) + { + ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("Invalid Node Id '%u'.", nodeIdValue))); + } + + workersForPlacementList = + lappend(workersForPlacementList, (void *) workerNode); + } + + return workersForPlacementList; +} + + +/* + * SplitShard API to split a given shard (or shard group) based on specified split points + * to a set of destination nodes. + * 'splitMode' : Mode of split operation. + * 'splitOperation' : Customer operation that triggered split. + * 'shardInterval' : Source shard interval to be split. + * 'shardSplitPointsList' : Split Points list for the source 'shardInterval'. + * 'nodeIdsForPlacementList' : Placement list corresponding to split children. + * 'distributionColumnList' : Maps relation IDs to distribution columns. + * If not specified, the distribution column is read + * from the metadata. + * 'colocatedShardIntervalList' : Shard interval list for colocation group. (only used for + * create_distributed_table_concurrently). + * 'targetColocationId' : Specifies the colocation ID (only used for + * create_distributed_table_concurrently). + */ +void +SplitShard(SplitMode splitMode, + SplitOperation splitOperation, + uint64 shardIdToSplit, + List *shardSplitPointsList, + List *nodeIdsForPlacementList, + DistributionColumnMap *distributionColumnOverrides, + List *colocatedShardIntervalList, + uint32 targetColocationId) +{ + ErrorIfModificationAndSplitInTheSameTransaction(splitOperation); ShardInterval *shardIntervalToSplit = LoadShardInterval(shardIdToSplit); List *colocatedTableList = ColocatedTableList(shardIntervalToSplit->relationId); @@ -390,6 +475,7 @@ SplitShard(SplitMode splitMode, VerifyTablesHaveReplicaIdentity(colocatedTableList); } + /* Acquire global lock to prevent concurrent split on the same colocation group or relation */ Oid relationId = RelationIdForShard(shardIdToSplit); AcquirePlacementColocationLock(relationId, ExclusiveLock, "split"); @@ -406,76 +492,137 @@ SplitShard(SplitMode splitMode, LockRelationOid(colocatedTableId, ShareUpdateExclusiveLock); } - ErrorIfCannotSplitShard(SHARD_SPLIT_API, shardIntervalToSplit); + ErrorIfCannotSplitShard(splitOperation, shardIntervalToSplit); ErrorIfCannotSplitShardExtended( - SHARD_SPLIT_API, + splitOperation, shardIntervalToSplit, shardSplitPointsList, nodeIdsForPlacementList); - List *workersForPlacementList = NIL; - Datum nodeId; - foreach_int(nodeId, nodeIdsForPlacementList) + List *workersForPlacementList = GetWorkerNodesFromWorkerIds(nodeIdsForPlacementList); + + List *sourceColocatedShardIntervalList = NIL; + if (colocatedShardIntervalList == NIL) { - uint32 nodeIdValue = DatumGetUInt32(nodeId); - WorkerNode *workerNode = LookupNodeByNodeId(nodeIdValue); - - /* NodeId in Citus are unsigned and range from [1, 4294967296]. */ - if (nodeIdValue < 1 || workerNode == NULL) - { - ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), - errmsg("Invalid Node Id '%u'.", nodeIdValue))); - } - - workersForPlacementList = - lappend(workersForPlacementList, (void *) workerNode); + sourceColocatedShardIntervalList = ColocatedShardIntervalList( + shardIntervalToSplit); + } + else + { + sourceColocatedShardIntervalList = colocatedShardIntervalList; } - /* Start operation to prepare for generating cleanup records */ - StartNewOperationNeedingCleanup(); + /* use the user-specified shard ID as the split workflow ID */ + uint64 splitWorkflowId = shardIntervalToSplit->shardId; if (splitMode == BLOCKING_SPLIT) { EnsureReferenceTablesExistOnAllNodesExtended(TRANSFER_MODE_BLOCK_WRITES); BlockingShardSplit( splitOperation, - shardIntervalToSplit, + splitWorkflowId, + sourceColocatedShardIntervalList, shardSplitPointsList, - workersForPlacementList); + workersForPlacementList, + distributionColumnOverrides); } else { NonBlockingShardSplit( splitOperation, - shardIntervalToSplit, + splitWorkflowId, + sourceColocatedShardIntervalList, shardSplitPointsList, - workersForPlacementList); + workersForPlacementList, + distributionColumnOverrides, + targetColocationId); PlacementMovedUsingLogicalReplicationInTX = true; } +} - bool isSuccess = true; - CompleteNewOperationNeedingCleanup(isSuccess); + +/* + * ShardIntervalHashCode computes the hash code for a Shardinterval using + * shardId. + */ +static uint32 +ShardIntervalHashCode(const void *key, Size keySize) +{ + const ShardInterval *shardInterval = (const ShardInterval *) key; + const uint64 *shardId = &(shardInterval->shardId); + + /* standard hash function outlined in Effective Java, Item 8 */ + uint32 result = 17; + result = 37 * result + tag_hash(shardId, sizeof(uint64)); + + return result; +} + + +/* + * ShardIntervalHashCompare compares two shard intervals using shard id. + */ +static int +ShardIntervalHashCompare(const void *lhsKey, const void *rhsKey, Size keySize) +{ + const ShardInterval *intervalLhs = (const ShardInterval *) lhsKey; + const ShardInterval *intervalRhs = (const ShardInterval *) rhsKey; + + int shardIdCompare = 0; + + /* first, compare by shard id */ + if (intervalLhs->shardId < intervalRhs->shardId) + { + shardIdCompare = -1; + } + else if (intervalLhs->shardId > intervalRhs->shardId) + { + shardIdCompare = 1; + } + + return shardIdCompare; +} + + +/* Create an empty map that tracks ShardInterval -> Placement Node as created by workflow */ +static HTAB * +CreateEmptyMapForShardsCreatedByWorkflow() +{ + HASHCTL info = { 0 }; + info.keysize = sizeof(ShardInterval); + info.entrysize = sizeof(ShardCreatedByWorkflowEntry); + info.hash = ShardIntervalHashCode; + info.match = ShardIntervalHashCompare; + info.hcxt = CurrentMemoryContext; + + /* we don't have value field as it's a set */ + info.entrysize = info.keysize; + uint32 hashFlags = (HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + HTAB *splitChildrenCreatedByWorkflow = hash_create("Shard id to Node Placement Map", + 32, &info, hashFlags); + return splitChildrenCreatedByWorkflow; } /* * SplitShard API to split a given shard (or shard group) in blocking fashion * based on specified split points to a set of destination nodes. - * 'splitOperation' : Customer operation that triggered split. - * 'shardIntervalToSplit' : Source shard interval to be split. - * 'shardSplitPointsList' : Split Points list for the source 'shardInterval'. - * 'workersForPlacementList' : Placement list corresponding to split children. + * splitOperation : Customer operation that triggered split. + * splitWorkflowId : Number used to identify split workflow in names. + * sourceColocatedShardIntervalList : Source shard group to be split. + * shardSplitPointsList : Split Points list for the source 'shardInterval'. + * workersForPlacementList : Placement list corresponding to split children. */ static void BlockingShardSplit(SplitOperation splitOperation, - ShardInterval *shardIntervalToSplit, + uint64 splitWorkflowId, + List *sourceColocatedShardIntervalList, List *shardSplitPointsList, - List *workersForPlacementList) + List *workersForPlacementList, + DistributionColumnMap *distributionColumnOverrides) { - List *sourceColocatedShardIntervalList = ColocatedShardIntervalList( - shardIntervalToSplit); - BlockWritesToShardList(sourceColocatedShardIntervalList); /* First create shard interval metadata for split children */ @@ -484,25 +631,29 @@ BlockingShardSplit(SplitOperation splitOperation, shardSplitPointsList); /* Only single placement allowed (already validated RelationReplicationFactor = 1) */ - List *sourcePlacementList = ActiveShardPlacementList(shardIntervalToSplit->shardId); - Assert(sourcePlacementList->length == 1); - ShardPlacement *sourceShardPlacement = (ShardPlacement *) linitial( - sourcePlacementList); - WorkerNode *sourceShardToCopyNode = FindNodeWithNodeId(sourceShardPlacement->nodeId, - false /* missingOk */); + ShardInterval *firstShard = linitial(sourceColocatedShardIntervalList); + WorkerNode *sourceShardNode = + ActiveShardPlacementWorkerNode(firstShard->shardId); + HTAB *mapOfShardToPlacementCreatedByWorkflow = + CreateEmptyMapForShardsCreatedByWorkflow(); PG_TRY(); { - /* - * Physically create split children, perform split copy and create auxiliary structures. - * This includes: indexes, replicaIdentity. triggers and statistics. - * Foreign key constraints are created after Metadata changes (see CreateForeignKeyConstraints). - */ - CreateAndCopySplitShardsForShardGroup( - sourceShardToCopyNode, - sourceColocatedShardIntervalList, - shardGroupSplitIntervalListList, - workersForPlacementList); + /* Physically create split children. */ + CreateSplitShardsForShardGroup(mapOfShardToPlacementCreatedByWorkflow, + shardGroupSplitIntervalListList, + workersForPlacementList); + + /* For Blocking split, copy isn't snapshotted */ + char *snapshotName = NULL; + DoSplitCopy(sourceShardNode, sourceColocatedShardIntervalList, + shardGroupSplitIntervalListList, workersForPlacementList, + snapshotName, distributionColumnOverrides); + + /* Create auxiliary structures (indexes, stats, replicaindentities, triggers) */ + CreateAuxiliaryStructuresForShardGroup(shardGroupSplitIntervalListList, + workersForPlacementList, + true /* includeReplicaIdentity*/); /* * Up to this point, we performed various subtransactions that may @@ -539,69 +690,21 @@ BlockingShardSplit(SplitOperation splitOperation, ShutdownAllConnections(); /* Do a best effort cleanup of shards created on workers in the above block */ - bool isSuccess = false; - CompleteNewOperationNeedingCleanup(isSuccess); + TryDropSplitShardsOnFailure(mapOfShardToPlacementCreatedByWorkflow); PG_RE_THROW(); } PG_END_TRY(); + CitusInvalidateRelcacheByRelid(DistShardRelationId()); } -/* Check if a relation with given name already exists on the worker node */ -static bool -CheckIfRelationWithSameNameExists(ShardInterval *shardInterval, WorkerNode *workerNode) -{ - char *schemaName = get_namespace_name( - get_rel_namespace(shardInterval->relationId)); - char *shardName = get_rel_name(shardInterval->relationId); - AppendShardIdToName(&shardName, shardInterval->shardId); - - StringInfo checkShardExistsQuery = makeStringInfo(); - appendStringInfo(checkShardExistsQuery, - "SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname = '%s' AND tablename = '%s');", - schemaName, - shardName); - - int connectionFlags = 0; - MultiConnection *connection = GetNodeUserDatabaseConnection(connectionFlags, - workerNode->workerName, - workerNode->workerPort, - CitusExtensionOwnerName(), - get_database_name( - MyDatabaseId)); - - PGresult *result = NULL; - int queryResult = ExecuteOptionalRemoteCommand(connection, - checkShardExistsQuery->data, &result); - if (queryResult != RESPONSE_OKAY || !IsResponseOK(result) || PQntuples(result) != 1) - { - ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), - errmsg( - "Cannot check if relation %s already exists for split on worker %s:%d", - ConstructQualifiedShardName(shardInterval), - connection->hostname, - connection->port))); - - PQclear(result); - ForgetResults(connection); - - return false; - } - - char *checkExists = PQgetvalue(result, 0, 0); - PQclear(result); - ForgetResults(connection); - - return strcmp(checkExists, "t") == 0; -} - - /* Create ShardGroup split children on a list of corresponding workers. */ static void -CreateSplitShardsForShardGroup(List *shardGroupSplitIntervalListList, +CreateSplitShardsForShardGroup(HTAB *mapOfShardToPlacementCreatedByWorkflow, + List *shardGroupSplitIntervalListList, List *workersForPlacementList) { /* @@ -629,35 +732,16 @@ CreateSplitShardsForShardGroup(List *shardGroupSplitIntervalListList, splitShardCreationCommandList, shardInterval->shardId); - /* Log resource for cleanup in case of failure only. - * Before we log a record, do a best effort check to see if a shard with same name exists. - * This is because, it will cause shard creation to fail and we will end up cleaning the - * old shard. We don't want that. - */ - bool relationExists = CheckIfRelationWithSameNameExists(shardInterval, - workerPlacementNode); + /* Create new split child shard on the specified placement list */ + CreateObjectOnPlacement(splitShardCreationCommandList, workerPlacementNode); - if (relationExists) - { - ereport(ERROR, (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("Relation %s already exists on worker %s:%d.", - ConstructQualifiedShardName(shardInterval), - workerPlacementNode->workerName, - workerPlacementNode->workerPort))); - } - else - { - CleanupPolicy policy = CLEANUP_ON_FAILURE; - InsertCleanupRecordInSubtransaction(CLEANUP_SHARD_PLACEMENT, - ConstructQualifiedShardName( - shardInterval), - workerPlacementNode->groupId, - policy); - - /* Create new split child shard on the specified placement list */ - CreateObjectOnPlacement(splitShardCreationCommandList, - workerPlacementNode); - } + ShardCreatedByWorkflowEntry entry; + entry.shardIntervalKey = shardInterval; + entry.workerNodeValue = workerPlacementNode; + bool found = false; + hash_search(mapOfShardToPlacementCreatedByWorkflow, &entry, HASH_ENTER, + &found); + Assert(!found); } } } @@ -736,31 +820,6 @@ CreateAuxiliaryStructuresForShardGroup(List *shardGroupSplitIntervalListList, } -/* - * Create ShardGroup split children, perform copy and create auxiliary structures - * on a list of corresponding workers. - */ -static void -CreateAndCopySplitShardsForShardGroup(WorkerNode *sourceShardNode, - List *sourceColocatedShardIntervalList, - List *shardGroupSplitIntervalListList, - List *workersForPlacementList) -{ - CreateSplitShardsForShardGroup(shardGroupSplitIntervalListList, - workersForPlacementList); - - /* For Blocking split, copy isn't snapshotted */ - char *snapshotName = NULL; - DoSplitCopy(sourceShardNode, sourceColocatedShardIntervalList, - shardGroupSplitIntervalListList, workersForPlacementList, snapshotName); - - /* Create auxiliary structures (indexes, stats, replicaindentities, triggers) */ - CreateAuxiliaryStructuresForShardGroup(shardGroupSplitIntervalListList, - workersForPlacementList, - true /* includeReplicaIdentity*/); -} - - /* * Perform Split Copy from source shard(s) to split children. * 'sourceShardNode' : Source shard worker node. @@ -771,7 +830,7 @@ CreateAndCopySplitShardsForShardGroup(WorkerNode *sourceShardNode, static void DoSplitCopy(WorkerNode *sourceShardNode, List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, List *destinationWorkerNodesList, - char *snapShotName) + char *snapShotName, DistributionColumnMap *distributionColumnOverrides) { ShardInterval *sourceShardIntervalToCopy = NULL; List *splitShardIntervalList = NIL; @@ -786,25 +845,40 @@ DoSplitCopy(WorkerNode *sourceShardNode, List *sourceColocatedShardIntervalList, * data themselves. Their partitions do contain data, but those are * different colocated shards that will be copied seperately. */ - if (!PartitionedTable(sourceShardIntervalToCopy->relationId)) + if (PartitionedTable(sourceShardIntervalToCopy->relationId)) { - StringInfo splitCopyUdfCommand = CreateSplitCopyCommand( - sourceShardIntervalToCopy, - splitShardIntervalList, - destinationWorkerNodesList); - - /* Create copy task. Snapshot name is required for nonblocking splits */ - Task *splitCopyTask = CreateSplitCopyTask(splitCopyUdfCommand, snapShotName, - taskId, - sourceShardIntervalToCopy->shardId); - - ShardPlacement *taskPlacement = CitusMakeNode(ShardPlacement); - SetPlacementNodeMetadata(taskPlacement, sourceShardNode); - splitCopyTask->taskPlacementList = list_make1(taskPlacement); - - splitCopyTaskList = lappend(splitCopyTaskList, splitCopyTask); - taskId++; + continue; } + + Oid relationId = sourceShardIntervalToCopy->relationId; + + Var *distributionColumn = + GetDistributionColumnWithOverrides(relationId, + distributionColumnOverrides); + Assert(distributionColumn != NULL); + + bool missingOK = false; + char *distributionColumnName = get_attname(relationId, + distributionColumn->varattno, + missingOK); + + StringInfo splitCopyUdfCommand = CreateSplitCopyCommand( + sourceShardIntervalToCopy, + distributionColumnName, + splitShardIntervalList, + destinationWorkerNodesList); + + /* Create copy task. Snapshot name is required for nonblocking splits */ + Task *splitCopyTask = CreateSplitCopyTask(splitCopyUdfCommand, snapShotName, + taskId, + sourceShardIntervalToCopy->shardId); + + ShardPlacement *taskPlacement = CitusMakeNode(ShardPlacement); + SetPlacementNodeMetadata(taskPlacement, sourceShardNode); + splitCopyTask->taskPlacementList = list_make1(taskPlacement); + + splitCopyTaskList = lappend(splitCopyTaskList, splitCopyTask); + taskId++; } ExecuteTaskListOutsideTransaction(ROW_MODIFY_NONE, splitCopyTaskList, @@ -837,6 +911,7 @@ DoSplitCopy(WorkerNode *sourceShardNode, List *sourceColocatedShardIntervalList, */ static StringInfo CreateSplitCopyCommand(ShardInterval *sourceShardSplitInterval, + char *distributionColumnName, List *splitChildrenShardIntervalList, List *destinationWorkerNodesList) { @@ -868,8 +943,9 @@ CreateSplitCopyCommand(ShardInterval *sourceShardSplitInterval, appendStringInfo(splitCopyInfoArray, "]"); StringInfo splitCopyUdf = makeStringInfo(); - appendStringInfo(splitCopyUdf, "SELECT pg_catalog.worker_split_copy(%lu, %s);", + appendStringInfo(splitCopyUdf, "SELECT pg_catalog.worker_split_copy(%lu, %s, %s);", sourceShardSplitInterval->shardId, + quote_literal_cstr(distributionColumnName), splitCopyInfoArray->data); return splitCopyUdf; @@ -979,8 +1055,19 @@ CreateSplitIntervalsForShard(ShardInterval *sourceShard, int shardIntervalCount = list_length(splitPointsForShard) + 1; ListCell *splitPointCell = list_head(splitPointsForShard); int32 splitParentMaxValue = DatumGetInt32(sourceShard->maxValue); - int32 currentSplitChildMinValue = DatumGetInt32(sourceShard->minValue); + + /* if we are splitting a Citus local table, assume whole shard range */ + if (!sourceShard->maxValueExists) + { + splitParentMaxValue = PG_INT32_MAX; + } + + if (!sourceShard->minValueExists) + { + currentSplitChildMinValue = PG_INT32_MIN; + } + for (int index = 0; index < shardIntervalCount; index++) { ShardInterval *splitChildShardInterval = CopyShardInterval(sourceShard); @@ -1011,6 +1098,54 @@ CreateSplitIntervalsForShard(ShardInterval *sourceShard, } +/* + * UpdateDistributionColumnsForShardGroup globally updates the pg_dist_partition metadata + * for each relation that has a shard in colocatedShardList. + * + * This is used primarily for Citus local -> distributed table conversion + * in create_distributed_table_concurrently. + * + * It would be nicer to keep this separate from shard split, but we need to do the + * update at exactly the right point in the shard split process, namely after + * replication slot creation and before inserting shard metadata, which itself + * needs to happen before foreign key creation (mainly because the foreign key + * functions depend on metadata). + */ +static void +UpdateDistributionColumnsForShardGroup(List *colocatedShardList, + DistributionColumnMap *distributionColumnMap, + char distributionMethod, + int shardCount, + uint32 colocationId) +{ + ShardInterval *shardInterval = NULL; + foreach_ptr(shardInterval, colocatedShardList) + { + Oid relationId = shardInterval->relationId; + Var *distributionColumn = GetDistributionColumnFromMap(distributionColumnMap, + relationId); + + /* we should have an entry for every relation ID in the colocation group */ + Assert(distributionColumn != NULL); + + if (colocationId == INVALID_COLOCATION_ID) + { + /* + * Getting here with an invalid co-location ID means that no + * appropriate co-location group exists yet. + */ + colocationId = CreateColocationGroup(shardCount, + ShardReplicationFactor, + distributionColumn->vartype, + distributionColumn->varcollid); + } + + UpdateDistributionColumnGlobally(relationId, distributionMethod, + distributionColumn, colocationId); + } +} + + /* * Insert new shard and placement metadata. * Sync the Metadata with all nodes if enabled. @@ -1201,37 +1336,20 @@ DropShardList(List *shardIntervalList) /* get shard name */ char *qualifiedShardName = ConstructQualifiedShardName(shardInterval); - if (DeferShardDeleteOnSplit) + char storageType = shardInterval->storageType; + if (storageType == SHARD_STORAGE_TABLE) { - /* Log shard in pg_dist_cleanup. - * Parent shards are to be dropped only on sucess after split workflow is complete, - * so mark the policy as 'CLEANUP_DEFERRED_ON_SUCCESS'. - * We also log cleanup record in the current transaction. If the current transaction rolls back, - * we do not generate a record at all. - */ - CleanupPolicy policy = CLEANUP_DEFERRED_ON_SUCCESS; - InsertCleanupRecordInCurrentTransaction(CLEANUP_SHARD_PLACEMENT, - qualifiedShardName, - placement->groupId, - policy); + appendStringInfo(dropQuery, DROP_REGULAR_TABLE_COMMAND, + qualifiedShardName); } - else + else if (storageType == SHARD_STORAGE_FOREIGN) { - char storageType = shardInterval->storageType; - if (storageType == SHARD_STORAGE_TABLE) - { - appendStringInfo(dropQuery, DROP_REGULAR_TABLE_COMMAND, - qualifiedShardName); - } - else if (storageType == SHARD_STORAGE_FOREIGN) - { - appendStringInfo(dropQuery, DROP_FOREIGN_TABLE_COMMAND, - qualifiedShardName); - } + appendStringInfo(dropQuery, DROP_FOREIGN_TABLE_COMMAND, + qualifiedShardName); + } - /* drop old shard */ - SendCommandToWorker(workerName, workerPort, dropQuery->data); - } + /* drop old shard */ + SendCommandToWorker(workerName, workerPort, dropQuery->data); } /* delete shard row */ @@ -1241,32 +1359,117 @@ DropShardList(List *shardIntervalList) /* - * SplitShard API to split a given shard (or shard group) in non-blocking fashion - * based on specified split points to a set of destination nodes. - * splitOperation : Customer operation that triggered split. - * shardIntervalToSplit : Source shard interval to be split. - * shardSplitPointsList : Split Points list for the source 'shardInterval'. - * workersForPlacementList : Placement list corresponding to split children. + * In case of failure, TryDropSplitShardsOnFailure drops in-progress shard placements from both the + * coordinator and mx nodes. */ static void -NonBlockingShardSplit(SplitOperation splitOperation, - ShardInterval *shardIntervalToSplit, - List *shardSplitPointsList, - List *workersForPlacementList) +TryDropSplitShardsOnFailure(HTAB *mapOfShardToPlacementCreatedByWorkflow) { + HASH_SEQ_STATUS status; + ShardCreatedByWorkflowEntry *entry; + + hash_seq_init(&status, mapOfShardToPlacementCreatedByWorkflow); + while ((entry = (ShardCreatedByWorkflowEntry *) hash_seq_search(&status)) != 0) + { + ShardInterval *shardInterval = entry->shardIntervalKey; + WorkerNode *workerPlacementNode = entry->workerNodeValue; + + char *qualifiedShardName = ConstructQualifiedShardName(shardInterval); + StringInfo dropShardQuery = makeStringInfo(); + + /* Caller enforces that foreign tables cannot be split (use DROP_REGULAR_TABLE_COMMAND) */ + appendStringInfo(dropShardQuery, DROP_REGULAR_TABLE_COMMAND, + qualifiedShardName); + + int connectionFlags = FOR_DDL; + connectionFlags |= OUTSIDE_TRANSACTION; + MultiConnection *connnection = GetNodeUserDatabaseConnection( + connectionFlags, + workerPlacementNode->workerName, + workerPlacementNode->workerPort, + CurrentUserName(), + NULL /* databaseName */); + + /* + * Perform a drop in best effort manner. + * The shard may or may not exist and the connection could have died. + */ + ExecuteOptionalRemoteCommand( + connnection, + dropShardQuery->data, + NULL /* pgResult */); + } +} + + +/* + * AcquireNonblockingSplitLock does not allow concurrent nonblocking splits, because we share memory and + * replication slots. + */ +static void +AcquireNonblockingSplitLock(Oid relationId) +{ + LOCKTAG tag; + const bool sessionLock = false; + const bool dontWait = true; + + SET_LOCKTAG_CITUS_OPERATION(tag, CITUS_NONBLOCKING_SPLIT); + + LockAcquireResult lockAcquired = LockAcquire(&tag, ExclusiveLock, sessionLock, + dontWait); + if (!lockAcquired) + { + ereport(ERROR, (errmsg("could not acquire the lock required to split " + "concurrently %s.", generate_qualified_relation_name( + relationId)), + errdetail("It means that either a concurrent shard move " + "or distributed table creation is happening."), + errhint("Make sure that the concurrent operation has " + "finished and re-run the command"))); + } +} + + +/* + * SplitShard API to split a given shard (or shard group) in non-blocking fashion + * based on specified split points to a set of destination nodes. + * splitOperation : Customer operation that triggered split. + * splitWorkflowId : Number used to identify split workflow in names. + * sourceColocatedShardIntervalList : Source shard group to be split. + * shardSplitPointsList : Split Points list for the source 'shardInterval'. + * workersForPlacementList : Placement list corresponding to split children. + * distributionColumnList : Maps relation IDs to distribution columns. + * If not specified, the distribution column is read + * from the metadata. + * targetColocationId : Specifies the colocation ID (only used for + * create_distributed_table_concurrently). + */ +void +NonBlockingShardSplit(SplitOperation splitOperation, + uint64 splitWorkflowId, + List *sourceColocatedShardIntervalList, + List *shardSplitPointsList, + List *workersForPlacementList, + DistributionColumnMap *distributionColumnOverrides, + uint32 targetColocationId) +{ + ErrorIfMultipleNonblockingMoveSplitInTheSameTransaction(); + char *superUser = CitusExtensionOwnerName(); char *databaseName = get_database_name(MyDatabaseId); - List *sourceColocatedShardIntervalList = ColocatedShardIntervalList( - shardIntervalToSplit); - /* First create shard interval metadata for split children */ List *shardGroupSplitIntervalListList = CreateSplitIntervalsForShardGroup( sourceColocatedShardIntervalList, shardSplitPointsList); - WorkerNode *sourceShardToCopyNode = ActiveShardPlacementWorkerNode( - shardIntervalToSplit->shardId); + ShardInterval *firstShard = linitial(sourceColocatedShardIntervalList); + + /* Acquire global lock to prevent concurrent nonblocking splits */ + AcquireNonblockingSplitLock(firstShard->relationId); + + WorkerNode *sourceShardToCopyNode = + ActiveShardPlacementWorkerNode(firstShard->shardId); /* Create hashmap to group shards for publication-subscription management */ HTAB *publicationInfoHash = CreateShardSplitInfoMapForPublication( @@ -1285,6 +1488,11 @@ NonBlockingShardSplit(SplitOperation splitOperation, databaseName); ClaimConnectionExclusively(sourceConnection); + HTAB *mapOfShardToPlacementCreatedByWorkflow = + CreateEmptyMapForShardsCreatedByWorkflow(); + + HTAB *mapOfDummyShardToPlacement = CreateSimpleHash(NodeAndOwner, + GroupedShardSplitInfos); MultiConnection *sourceReplicationConnection = GetReplicationConnection(sourceShardToCopyNode->workerName, sourceShardToCopyNode->workerPort); @@ -1293,7 +1501,8 @@ NonBlockingShardSplit(SplitOperation splitOperation, PG_TRY(); { /* 1) Physically create split children. */ - CreateSplitShardsForShardGroup(shardGroupSplitIntervalListList, + CreateSplitShardsForShardGroup(mapOfShardToPlacementCreatedByWorkflow, + shardGroupSplitIntervalListList, workersForPlacementList); /* @@ -1301,10 +1510,8 @@ NonBlockingShardSplit(SplitOperation splitOperation, * Refer to the comment section of 'CreateDummyShardsForShardGroup' for indepth * information. */ - HTAB *mapOfPlacementToDummyShardList = CreateSimpleHash(NodeAndOwner, - GroupedShardSplitInfos); CreateDummyShardsForShardGroup( - mapOfPlacementToDummyShardList, + mapOfDummyShardToPlacement, sourceColocatedShardIntervalList, shardGroupSplitIntervalListList, sourceShardToCopyNode, @@ -1319,18 +1526,18 @@ NonBlockingShardSplit(SplitOperation splitOperation, * initial COPY phase, like we do for the replica identities on the * target shards. */ - CreateReplicaIdentitiesForDummyShards(mapOfPlacementToDummyShardList); + CreateReplicaIdentitiesForDummyShards(mapOfDummyShardToPlacement); /* 4) Create Publications. */ CreatePublications(sourceConnection, publicationInfoHash); - /* 5) Execute 'worker_split_shard_replication_setup UDF */ List *replicationSlotInfoList = ExecuteSplitShardReplicationSetupUDF( sourceShardToCopyNode, sourceColocatedShardIntervalList, shardGroupSplitIntervalListList, - workersForPlacementList); + workersForPlacementList, + distributionColumnOverrides); /* * Subscriber flow starts from here. @@ -1372,7 +1579,7 @@ NonBlockingShardSplit(SplitOperation splitOperation, /* 8) Do snapshotted Copy */ DoSplitCopy(sourceShardToCopyNode, sourceColocatedShardIntervalList, shardGroupSplitIntervalListList, workersForPlacementList, - snapshot); + snapshot, distributionColumnOverrides); /* * 9) Create replica identities, this needs to be done before enabling @@ -1425,7 +1632,40 @@ NonBlockingShardSplit(SplitOperation splitOperation, */ DropShardList(sourceColocatedShardIntervalList); - /* 21) Insert new shard and placement metdata */ + /* + * 21) In case of create_distributed_table_concurrently, which converts + * a Citus local table to a distributed table, update the distributed + * table metadata now. + * + * We would rather have this be outside of the scope of NonBlockingShardSplit, + * but we cannot make metadata changes before replication slot creation, and + * we cannot create the replication slot before creating new shards and + * corresponding publications, because the decoder uses a catalog snapshot + * from the time of the slot creation, which means it would not be able to see + * the shards or publications when replication starts if it was created before. + * + * We also cannot easily move metadata changes to be after this function, + * because CreateForeignKeyConstraints relies on accurate metadata and + * we also want to perform the clean-up logic in PG_CATCH in case of + * failure. + * + * Hence, this appears to be the only suitable spot for updating + * pg_dist_partition and pg_dist_colocation. + */ + if (splitOperation == CREATE_DISTRIBUTED_TABLE) + { + /* we currently only use split for hash-distributed tables */ + char distributionMethod = DISTRIBUTE_BY_HASH; + int shardCount = list_length(shardSplitPointsList) + 1; + + UpdateDistributionColumnsForShardGroup(sourceColocatedShardIntervalList, + distributionColumnOverrides, + distributionMethod, + shardCount, + targetColocationId); + } + + /* 22) Insert new shard and placement metdata */ InsertSplitChildrenShardMetadata(shardGroupSplitIntervalListList, workersForPlacementList); @@ -1433,20 +1673,31 @@ NonBlockingShardSplit(SplitOperation splitOperation, workersForPlacementList); /* - * 22) Create foreign keys if exists after the metadata changes happening in + * 23) Create foreign keys if exists after the metadata changes happening in * DropShardList() and InsertSplitChildrenShardMetadata() because the foreign * key creation depends on the new metadata. */ CreateForeignKeyConstraints(shardGroupSplitIntervalListList, workersForPlacementList); - /* 24) Close source connection */ + /* + * 24) Drop dummy shards. + */ + DropDummyShards(mapOfDummyShardToPlacement); + + /* + * 24) Release shared memory allocated by worker_split_shard_replication_setup udf + * at source node. + */ + ExecuteSplitShardReleaseSharedMemory(sourceShardToCopyNode); + + /* 25) Close source connection */ CloseConnection(sourceConnection); - /* 25) Close all subscriber connections */ + /* 26) Close all subscriber connections */ CloseGroupedLogicalRepTargetsConnections(groupedLogicalRepTargetsHash); - /* 26) Close connection of template replication slot */ + /* 27) Close connection of template replication slot */ CloseConnection(sourceReplicationConnection); } PG_CATCH(); @@ -1454,14 +1705,14 @@ NonBlockingShardSplit(SplitOperation splitOperation, /* end ongoing transactions to enable us to clean up */ ShutdownAllConnections(); - /* Do a best effort cleanup of shards created on workers in the above block - * TODO(niupre): We don't need to do this once shard cleaner can clean replication - * artifacts. - */ + /* Do a best effort cleanup of shards created on workers in the above block */ + TryDropSplitShardsOnFailure(mapOfShardToPlacementCreatedByWorkflow); + DropAllLogicalReplicationLeftovers(SHARD_SPLIT); - bool isSuccess = false; - CompleteNewOperationNeedingCleanup(isSuccess); + DropDummyShards(mapOfDummyShardToPlacement); + + ExecuteSplitShardReleaseSharedMemory(sourceShardToCopyNode); PG_RE_THROW(); } @@ -1492,7 +1743,7 @@ NonBlockingShardSplit(SplitOperation splitOperation, * Note 2 : Given there is an overlap of source and destination in Worker0, Shard1_1 and Shard2_1 need not be created. */ static void -CreateDummyShardsForShardGroup(HTAB *mapOfPlacementToDummyShardList, +CreateDummyShardsForShardGroup(HTAB *mapOfDummyShardToPlacement, List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, WorkerNode *sourceWorkerNode, @@ -1529,43 +1780,13 @@ CreateDummyShardsForShardGroup(HTAB *mapOfPlacementToDummyShardList, splitShardCreationCommandList, shardInterval->shardId); - /* Log resource for cleanup in case of failure only. - * Before we log a record, do a best effort check to see if a shard with same name exists. - * This is because, it will cause shard creation to fail and we will end up cleaning the - * old shard. We don't want that. - */ - bool relationExists = CheckIfRelationWithSameNameExists(shardInterval, - workerPlacementNode); + /* Create dummy source shard on the specified placement list */ + CreateObjectOnPlacement(splitShardCreationCommandList, workerPlacementNode); - if (relationExists) - { - ereport(ERROR, (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("Relation %s already exists on worker %s:%d.", - ConstructQualifiedShardName(shardInterval), - workerPlacementNode->workerName, - workerPlacementNode->workerPort))); - } - else - { - /* Log shard in pg_dist_cleanup. Given dummy shards are transient resources, - * we want to cleanup irrespective of operation success or failure. - */ - CleanupPolicy policy = CLEANUP_ALWAYS; - InsertCleanupRecordInSubtransaction(CLEANUP_SHARD_PLACEMENT, - ConstructQualifiedShardName( - shardInterval), - workerPlacementNode->groupId, - policy); - - /* Create dummy source shard on the specified placement list */ - CreateObjectOnPlacement(splitShardCreationCommandList, - workerPlacementNode); - - /* Add dummy source shard entry created for placement node in map */ - AddDummyShardEntryInMap(mapOfPlacementToDummyShardList, - workerPlacementNode->nodeId, - shardInterval); - } + /* Add dummy source shard entry created for placement node in map */ + AddDummyShardEntryInMap(mapOfDummyShardToPlacement, + workerPlacementNode->nodeId, + shardInterval); } } @@ -1594,42 +1815,12 @@ CreateDummyShardsForShardGroup(HTAB *mapOfPlacementToDummyShardList, splitShardCreationCommandList, shardInterval->shardId); - /* Log resource for cleanup in case of failure only. - * Before we log a record, do a best effort check to see if a shard with same name exists. - * This is because, it will cause shard creation to fail and we will end up cleaning the - * old shard. We don't want that. - */ - bool relationExists = CheckIfRelationWithSameNameExists(shardInterval, - sourceWorkerNode); + /* Create dummy split child shard on source worker node */ + CreateObjectOnPlacement(splitShardCreationCommandList, sourceWorkerNode); - if (relationExists) - { - ereport(ERROR, (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("Relation %s already exists on worker %s:%d.", - ConstructQualifiedShardName(shardInterval), - sourceWorkerNode->workerName, - sourceWorkerNode->workerPort))); - } - else - { - /* Log shard in pg_dist_cleanup. Given dummy shards are transient resources, - * we want to cleanup irrespective of operation success or failure. - */ - CleanupPolicy policy = CLEANUP_ALWAYS; - InsertCleanupRecordInSubtransaction(CLEANUP_SHARD_PLACEMENT, - ConstructQualifiedShardName( - shardInterval), - sourceWorkerNode->groupId, - policy); - - /* Create dummy split child shard on source worker node */ - CreateObjectOnPlacement(splitShardCreationCommandList, sourceWorkerNode); - - /* Add dummy split child shard entry created on source node */ - AddDummyShardEntryInMap(mapOfPlacementToDummyShardList, - sourceWorkerNode->nodeId, - shardInterval); - } + /* Add dummy split child shard entry created on source node */ + AddDummyShardEntryInMap(mapOfDummyShardToPlacement, sourceWorkerNode->nodeId, + shardInterval); } } } @@ -1674,12 +1865,14 @@ static List * ExecuteSplitShardReplicationSetupUDF(WorkerNode *sourceWorkerNode, List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, - List *destinationWorkerNodesList) + List *destinationWorkerNodesList, + DistributionColumnMap *distributionColumnOverrides) { StringInfo splitShardReplicationUDF = CreateSplitShardReplicationSetupUDF( sourceColocatedShardIntervalList, shardGroupSplitIntervalListList, - destinationWorkerNodesList); + destinationWorkerNodesList, + distributionColumnOverrides); /* Force a new connection to execute the UDF */ int connectionFlags = 0; @@ -1727,6 +1920,33 @@ ExecuteSplitShardReplicationSetupUDF(WorkerNode *sourceWorkerNode, } +/* + * ExecuteSplitShardReleaseSharedMemory releases dynamic shared memory + * at source node. + * As a part of non-blocking split workflow, worker_split_shard_replication_setup allocates + * shared memory to store split information. This has to be released after split completes(or fails). + */ +static void +ExecuteSplitShardReleaseSharedMemory(WorkerNode *sourceWorkerNode) +{ + char *superUser = CitusExtensionOwnerName(); + char *databaseName = get_database_name(MyDatabaseId); + + int connectionFlag = FORCE_NEW_CONNECTION; + MultiConnection *sourceConnection = GetNodeUserDatabaseConnection( + connectionFlag, + sourceWorkerNode->workerName, + sourceWorkerNode->workerPort, + superUser, + databaseName); + + StringInfo splitShardReleaseMemoryUDF = makeStringInfo(); + appendStringInfo(splitShardReleaseMemoryUDF, + "SELECT pg_catalog.worker_split_shard_release_dsm();"); + ExecuteCriticalRemoteCommand(sourceConnection, splitShardReleaseMemoryUDF->data); +} + + /* * CreateSplitShardReplicationSetupUDF creates and returns * parameterized 'worker_split_shard_replication_setup' UDF command. @@ -1749,7 +1969,8 @@ ExecuteSplitShardReplicationSetupUDF(WorkerNode *sourceWorkerNode, StringInfo CreateSplitShardReplicationSetupUDF(List *sourceColocatedShardIntervalList, List *shardGroupSplitIntervalListList, - List *destinationWorkerNodesList) + List *destinationWorkerNodesList, + DistributionColumnMap *distributionColumnOverrides) { StringInfo splitChildrenRows = makeStringInfo(); @@ -1761,11 +1982,15 @@ CreateSplitShardReplicationSetupUDF(List *sourceColocatedShardIntervalList, { int64 sourceShardId = sourceShardIntervalToCopy->shardId; Oid relationId = sourceShardIntervalToCopy->relationId; - Var *partitionColumn = DistPartitionKey(relationId); + + Var *distributionColumn = + GetDistributionColumnWithOverrides(relationId, + distributionColumnOverrides); bool missingOK = false; - char *partitionColumnName = - get_attname(relationId, partitionColumn->varattno, missingOK); + char *distributionColumnName = + get_attname(relationId, distributionColumn->varattno, + missingOK); ShardInterval *splitChildShardInterval = NULL; WorkerNode *destinationWorkerNode = NULL; @@ -1788,7 +2013,7 @@ CreateSplitShardReplicationSetupUDF(List *sourceColocatedShardIntervalList, appendStringInfo(splitChildrenRows, "ROW(%lu, %s, %lu, %s, %s, %u)::pg_catalog.split_shard_info", sourceShardId, - quote_literal_cstr(partitionColumnName), + quote_literal_cstr(distributionColumnName), splitChildShardInterval->shardId, quote_literal_cstr(minValueString->data), quote_literal_cstr(maxValueString->data), @@ -1851,8 +2076,7 @@ ParseReplicationSlotInfoFromResult(PGresult *result) * of logical replication. We cautiously delete only the dummy shards added in the DummyShardHashMap. */ static void -AddDummyShardEntryInMap(HTAB *mapOfPlacementToDummyShardList, - uint32 targetNodeId, +AddDummyShardEntryInMap(HTAB *mapOfDummyShardToPlacement, uint32 targetNodeId, ShardInterval *shardInterval) { NodeAndOwner key; @@ -1861,8 +2085,7 @@ AddDummyShardEntryInMap(HTAB *mapOfPlacementToDummyShardList, bool found = false; GroupedDummyShards *nodeMappingEntry = - (GroupedDummyShards *) hash_search(mapOfPlacementToDummyShardList, - &key, + (GroupedDummyShards *) hash_search(mapOfDummyShardToPlacement, &key, HASH_ENTER, &found); if (!found) @@ -1875,6 +2098,68 @@ AddDummyShardEntryInMap(HTAB *mapOfPlacementToDummyShardList, } +/* + * DropDummyShards traverses the dummy shard map and drops shard at given node. + * It fails if the shard cannot be dropped. + */ +static void +DropDummyShards(HTAB *mapOfDummyShardToPlacement) +{ + HASH_SEQ_STATUS status; + hash_seq_init(&status, mapOfDummyShardToPlacement); + + GroupedDummyShards *entry = NULL; + while ((entry = (GroupedDummyShards *) hash_seq_search(&status)) != NULL) + { + uint32 nodeId = entry->key.nodeId; + WorkerNode *shardToBeDroppedNode = FindNodeWithNodeId(nodeId, + false /* missingOk */); + + int connectionFlags = FOR_DDL; + connectionFlags |= OUTSIDE_TRANSACTION; + MultiConnection *connection = GetNodeUserDatabaseConnection( + connectionFlags, + shardToBeDroppedNode->workerName, + shardToBeDroppedNode->workerPort, + CurrentUserName(), + NULL /* databaseName */); + + List *dummyShardIntervalList = entry->shardIntervals; + ShardInterval *shardInterval = NULL; + foreach_ptr(shardInterval, dummyShardIntervalList) + { + DropDummyShard(connection, shardInterval); + } + + CloseConnection(connection); + } +} + + +/* + * DropDummyShard drops a given shard on the node connection. + * It fails if the shard cannot be dropped. + */ +static void +DropDummyShard(MultiConnection *connection, ShardInterval *shardInterval) +{ + char *qualifiedShardName = ConstructQualifiedShardName(shardInterval); + StringInfo dropShardQuery = makeStringInfo(); + + /* Caller enforces that foreign tables cannot be split (use DROP_REGULAR_TABLE_COMMAND) */ + appendStringInfo(dropShardQuery, DROP_REGULAR_TABLE_COMMAND, + qualifiedShardName); + + /* + * Since the dummy shard is expected to be present on the given node, + * fail if it cannot be dropped during cleanup. + */ + ExecuteCriticalRemoteCommand( + connection, + dropShardQuery->data); +} + + /* * CreateReplicaIdentitiesForDummyShards creates replica indentities for split * dummy shards. diff --git a/src/backend/distributed/operations/worker_split_copy_udf.c b/src/backend/distributed/operations/worker_split_copy_udf.c index 2b33654f9..fe20aeca2 100644 --- a/src/backend/distributed/operations/worker_split_copy_udf.c +++ b/src/backend/distributed/operations/worker_split_copy_udf.c @@ -9,15 +9,16 @@ #include "postgres.h" #include "pg_version_compat.h" +#include "distributed/citus_ruleutils.h" +#include "distributed/distribution_column.h" +#include "distributed/intermediate_results.h" +#include "distributed/listutils.h" +#include "distributed/multi_executor.h" +#include "distributed/utils/array_type.h" +#include "distributed/worker_shard_copy.h" #include "utils/lsyscache.h" #include "utils/array.h" #include "utils/builtins.h" -#include "distributed/utils/array_type.h" -#include "distributed/listutils.h" -#include "distributed/multi_executor.h" -#include "distributed/worker_shard_copy.h" -#include "distributed/intermediate_results.h" -#include "distributed/citus_ruleutils.h" PG_FUNCTION_INFO_V1(worker_split_copy); @@ -38,6 +39,7 @@ static DestReceiver ** CreateShardCopyDestReceivers(EState *estate, static DestReceiver * CreatePartitionedSplitCopyDestReceiver(EState *executor, ShardInterval * shardIntervalToSplitCopy, + char *partitionColumnName, List *splitCopyInfoList); static void BuildMinMaxRangeArrays(List *splitCopyInfoList, ArrayType **minValueArray, ArrayType **maxValueArray); @@ -54,7 +56,10 @@ worker_split_copy(PG_FUNCTION_ARGS) uint64 shardIdToSplitCopy = DatumGetUInt64(PG_GETARG_DATUM(0)); ShardInterval *shardIntervalToSplitCopy = LoadShardInterval(shardIdToSplitCopy); - ArrayType *splitCopyInfoArrayObject = PG_GETARG_ARRAYTYPE_P(1); + text *partitionColumnText = PG_GETARG_TEXT_P(1); + char *partitionColumnName = text_to_cstring(partitionColumnText); + + ArrayType *splitCopyInfoArrayObject = PG_GETARG_ARRAYTYPE_P(2); bool arrayHasNull = ARR_HASNULL(splitCopyInfoArrayObject); if (arrayHasNull) { @@ -82,6 +87,7 @@ worker_split_copy(PG_FUNCTION_ARGS) EState *executor = CreateExecutorState(); DestReceiver *splitCopyDestReceiver = CreatePartitionedSplitCopyDestReceiver(executor, shardIntervalToSplitCopy, + partitionColumnName, splitCopyInfoList); Oid sourceShardToCopySchemaOId = get_rel_namespace( @@ -228,6 +234,7 @@ CreateShardCopyDestReceivers(EState *estate, ShardInterval *shardIntervalToSplit static DestReceiver * CreatePartitionedSplitCopyDestReceiver(EState *estate, ShardInterval *shardIntervalToSplitCopy, + char *partitionColumnName, List *splitCopyInfoList) { /* Create underlying ShardCopyDestReceivers */ @@ -240,10 +247,17 @@ CreatePartitionedSplitCopyDestReceiver(EState *estate, ArrayType *minValuesArray = NULL; ArrayType *maxValuesArray = NULL; BuildMinMaxRangeArrays(splitCopyInfoList, &minValuesArray, &maxValuesArray); - CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry( - shardIntervalToSplitCopy->relationId); - char partitionMethod = cacheEntry->partitionMethod; - Var *partitionColumn = cacheEntry->partitionColumn; + + /* we currently only support hash-distribution */ + char partitionMethod = DISTRIBUTE_BY_HASH; + + /* synthetically build the partition column by looking at shard columns */ + uint64 shardId = shardIntervalToSplitCopy->shardId; + bool missingOK = false; + Oid shardRelationId = LookupShardRelationFromCatalog(shardId, missingOK); + Var *partitionColumn = BuildDistributionKeyFromColumnName(shardRelationId, + partitionColumnName, + AccessShareLock); CitusTableCacheEntry *shardSearchInfo = QueryTupleShardSearchInfo(minValuesArray, maxValuesArray, diff --git a/src/backend/distributed/operations/worker_split_shard_release_dsm_udf.c b/src/backend/distributed/operations/worker_split_shard_release_dsm_udf.c new file mode 100644 index 000000000..94ce40cdb --- /dev/null +++ b/src/backend/distributed/operations/worker_split_shard_release_dsm_udf.c @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * worker_split_shard_release_dsm.c + * This file contains functions to release dynamic shared memory segment + * allocated during split workflow. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "distributed/shardinterval_utils.h" +#include "distributed/shardsplit_shared_memory.h" + + +/* declarations for dynamic loading */ +PG_FUNCTION_INFO_V1(worker_split_shard_release_dsm); + +Datum +worker_split_shard_release_dsm(PG_FUNCTION_ARGS) +{ + ReleaseSharedMemoryOfShardSplitInfo(); + PG_RETURN_VOID(); +} diff --git a/src/backend/distributed/operations/worker_split_shard_replication_setup_udf.c b/src/backend/distributed/operations/worker_split_shard_replication_setup_udf.c index 98d14c857..f4ffc6184 100644 --- a/src/backend/distributed/operations/worker_split_shard_replication_setup_udf.c +++ b/src/backend/distributed/operations/worker_split_shard_replication_setup_udf.c @@ -1,6 +1,6 @@ /*------------------------------------------------------------------------- * - * worker_split_shard_replication_setup.c + * worker_split_shard_replication_setup_udf.c * This file contains functions to setup information about list of shards * that are being split. * diff --git a/src/backend/distributed/planner/deparse_shard_query.c b/src/backend/distributed/planner/deparse_shard_query.c index 43a493a89..11338df65 100644 --- a/src/backend/distributed/planner/deparse_shard_query.c +++ b/src/backend/distributed/planner/deparse_shard_query.c @@ -137,7 +137,7 @@ RebuildQueryStrings(Job *workerJob) ereport(DEBUG4, (errmsg("query before rebuilding: %s", !isQueryObjectOrText ? "(null)" - : ApplyLogRedaction(TaskQueryString(task))))); + : TaskQueryString(task)))); UpdateTaskQueryString(query, task); @@ -148,7 +148,7 @@ RebuildQueryStrings(Job *workerJob) task->parametersInQueryStringResolved = workerJob->parametersInJobQueryResolved; ereport(DEBUG4, (errmsg("query after rebuilding: %s", - ApplyLogRedaction(TaskQueryString(task))))); + TaskQueryString(task)))); } } diff --git a/src/backend/distributed/planner/distributed_planner.c b/src/backend/distributed/planner/distributed_planner.c index d16aa6785..35664d7c7 100644 --- a/src/backend/distributed/planner/distributed_planner.c +++ b/src/backend/distributed/planner/distributed_planner.c @@ -74,6 +74,8 @@ static uint64 NextPlanId = 1; /* keep track of planner call stack levels */ int PlannerLevel = 0; +static void ErrorIfQueryHasMergeCommand(Query *queryTree); +static bool ContainsMergeCommandWalker(Node *node); static bool ListContainsDistributedTableRTE(List *rangeTableList, bool *maybeHasForeignDistributedTable); static bool IsUpdateOrDelete(Query *query); @@ -148,6 +150,12 @@ distributed_planner(Query *parse, /* this cursor flag could only be set when Citus has been loaded */ Assert(CitusHasBeenLoaded()); + /* + * We cannot have merge command for this path as well because + * there cannot be recursively planned merge command. + */ + Assert(!ContainsMergeCommandWalker((Node *) parse)); + needsDistributedPlanning = true; } else if (CitusHasBeenLoaded()) @@ -187,13 +195,20 @@ distributed_planner(Query *parse, planContext.originalQuery = copyObject(parse); - /* - * When there are partitioned tables (not applicable to fast path), - * pretend that they are regular tables to avoid unnecessary work - * in standard_planner. - */ + if (!fastPathRouterQuery) { + /* + * Fast path queries cannot have merge command, and we + * prevent the remaining here. + */ + ErrorIfQueryHasMergeCommand(parse); + + /* + * When there are partitioned tables (not applicable to fast path), + * pretend that they are regular tables to avoid unnecessary work + * in standard_planner. + */ bool setPartitionedTablesInherited = false; AdjustPartitioningForDistributedPlanning(rangeTableList, setPartitionedTablesInherited); @@ -287,6 +302,59 @@ distributed_planner(Query *parse, } +/* + * ErrorIfQueryHasMergeCommand walks over the query tree and throws error + * if there are any Merge command (e.g., CMD_MERGE) in the query tree. + */ +static void +ErrorIfQueryHasMergeCommand(Query *queryTree) +{ + /* + * Postgres currently doesn't support Merge queries inside subqueries and + * ctes, but lets be defensive and do query tree walk anyway. + * + * We do not call this path for fast-path queries to avoid this additional + * overhead. + */ + if (ContainsMergeCommandWalker((Node *) queryTree)) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("MERGE command is not supported on Citus tables yet"))); + } +} + + +/* + * ContainsMergeCommandWalker walks over the node and finds if there are any + * Merge command (e.g., CMD_MERGE) in the node. + */ +static bool +ContainsMergeCommandWalker(Node *node) +{ +#if PG_VERSION_NUM >= PG_VERSION_15 + if (node == NULL) + { + return false; + } + + if (IsA(node, Query)) + { + Query *query = (Query *) node; + if (query->commandType == CMD_MERGE) + { + return true; + } + + return query_tree_walker((Query *) node, ContainsMergeCommandWalker, NULL, 0); + } + + return expression_tree_walker(node, ContainsMergeCommandWalker, NULL); +#endif + + return false; +} + + /* * ExtractRangeTableEntryList is a wrapper around ExtractRangeTableEntryWalker. * The function traverses the input query and returns all the range table diff --git a/src/backend/distributed/planner/fast_path_router_planner.c b/src/backend/distributed/planner/fast_path_router_planner.c index 5d02be07c..5be585c67 100644 --- a/src/backend/distributed/planner/fast_path_router_planner.c +++ b/src/backend/distributed/planner/fast_path_router_planner.c @@ -162,6 +162,17 @@ FastPathRouterQuery(Query *query, Node **distributionKeyValue) return false; } +#if PG_VERSION_NUM >= PG_VERSION_15 + if (query->commandType == CMD_MERGE) + { + /* + * Citus doesn't support MERGE command, lets return + * early and explicitly for fast-path queries. + */ + return false; + } +#endif + /* * We want to deal with only very simple queries. Some of the * checks might be too restrictive, still we prefer this way. diff --git a/src/backend/distributed/planner/insert_select_planner.c b/src/backend/distributed/planner/insert_select_planner.c index 097b2bb81..157e04dc4 100644 --- a/src/backend/distributed/planner/insert_select_planner.c +++ b/src/backend/distributed/planner/insert_select_planner.c @@ -927,7 +927,7 @@ RouterModifyTaskForShardInterval(Query *originalQuery, deparse_shard_query(copiedQuery, distributedTableId, shardInterval->shardId, queryString); ereport(DEBUG2, (errmsg("distributed statement: %s", - ApplyLogRedaction(queryString->data)))); + queryString->data))); Task *modifyTask = CreateBasicTask(jobId, taskIdIndex, MODIFY_TASK, queryString->data); diff --git a/src/backend/distributed/planner/multi_join_order.c b/src/backend/distributed/planner/multi_join_order.c index cdfd94cc5..9b2342b20 100644 --- a/src/backend/distributed/planner/multi_join_order.c +++ b/src/backend/distributed/planner/multi_join_order.c @@ -627,7 +627,7 @@ PrintJoinOrderList(List *joinOrder) } ereport(LOG, (errmsg("join order: %s", - ApplyLogRedaction(printBuffer->data)))); + printBuffer->data))); } diff --git a/src/backend/distributed/planner/multi_logical_planner.c b/src/backend/distributed/planner/multi_logical_planner.c index 7e665b567..14dfa924f 100644 --- a/src/backend/distributed/planner/multi_logical_planner.c +++ b/src/backend/distributed/planner/multi_logical_planner.c @@ -1154,7 +1154,8 @@ HasComplexRangeTableType(Query *queryTree) if (rangeTableEntry->rtekind != RTE_RELATION && rangeTableEntry->rtekind != RTE_SUBQUERY && rangeTableEntry->rtekind != RTE_FUNCTION && - rangeTableEntry->rtekind != RTE_VALUES) + rangeTableEntry->rtekind != RTE_VALUES && + !IsJsonTableRTE(rangeTableEntry)) { hasComplexRangeTableType = true; } diff --git a/src/backend/distributed/planner/multi_physical_planner.c b/src/backend/distributed/planner/multi_physical_planner.c index df1f1bfcb..f909b48ce 100644 --- a/src/backend/distributed/planner/multi_physical_planner.c +++ b/src/backend/distributed/planner/multi_physical_planner.c @@ -490,6 +490,10 @@ RangePartitionJoinBaseRelationId(MultiJoin *joinNode) { partitionNode = (MultiPartition *) rightChildNode; } + else + { + Assert(false); + } Index baseTableId = partitionNode->splitPointTableId; MultiTable *baseTable = FindTableNode((MultiNode *) joinNode, baseTableId); @@ -575,12 +579,7 @@ BuildJobQuery(MultiNode *multiNode, List *dependentJobList) Job *job = (Job *) linitial(dependentJobList); if (CitusIsA(job, MapMergeJob)) { - MapMergeJob *mapMergeJob = (MapMergeJob *) job; isRepartitionJoin = true; - if (mapMergeJob->reduceQuery) - { - updateColumnAttributes = false; - } } } @@ -2566,7 +2565,7 @@ QueryPushdownTaskCreate(Query *originalQuery, int shardIndex, { pg_get_query_def(taskQuery, queryString); ereport(DEBUG4, (errmsg("distributed statement: %s", - ApplyLogRedaction(queryString->data)))); + queryString->data))); SetTaskQueryString(subqueryTask, queryString->data); } @@ -2721,7 +2720,7 @@ SqlTaskList(Job *job) /* log the query string we generated */ ereport(DEBUG4, (errmsg("generated sql query for task %d", sqlTask->taskId), errdetail("query string: \"%s\"", - ApplyLogRedaction(sqlQueryString->data)))); + sqlQueryString->data))); sqlTask->anchorShardId = INVALID_SHARD_ID; if (anchorRangeTableBasedAssignment) @@ -3236,45 +3235,6 @@ BinaryOpExpression(Expr *clause, Node **leftOperand, Node **rightOperand) } -/* - * SimpleOpExpression checks that given expression is a simple operator - * expression. A simple operator expression is a binary operator expression with - * operands of a var and a non-null constant. - */ -bool -SimpleOpExpression(Expr *clause) -{ - Const *constantClause = NULL; - - Node *leftOperand; - Node *rightOperand; - if (!BinaryOpExpression(clause, &leftOperand, &rightOperand)) - { - return false; - } - - if (IsA(rightOperand, Const) && IsA(leftOperand, Var)) - { - constantClause = (Const *) rightOperand; - } - else if (IsA(leftOperand, Const) && IsA(rightOperand, Var)) - { - constantClause = (Const *) leftOperand; - } - else - { - return false; - } - - if (constantClause->constisnull) - { - return false; - } - - return true; -} - - /* * MakeInt4Column creates a column of int4 type with invalid table id and max * attribute number. @@ -4710,18 +4670,13 @@ MergeTaskList(MapMergeJob *mapMergeJob, List *mapTaskList, uint32 taskIdIndex) for (uint32 partitionId = initialPartitionId; partitionId < partitionCount; partitionId++) { - Task *mergeTask = NULL; List *mapOutputFetchTaskList = NIL; ListCell *mapTaskCell = NULL; uint32 mergeTaskId = taskIdIndex; - Query *reduceQuery = mapMergeJob->reduceQuery; - if (reduceQuery == NULL) - { - /* create logical merge task (not executed, but useful for bookkeeping) */ - mergeTask = CreateBasicTask(jobId, mergeTaskId, MERGE_TASK, - ""); - } + /* create logical merge task (not executed, but useful for bookkeeping) */ + Task *mergeTask = CreateBasicTask(jobId, mergeTaskId, MERGE_TASK, + ""); mergeTask->partitionId = partitionId; taskIdIndex++; diff --git a/src/backend/distributed/planner/query_pushdown_planning.c b/src/backend/distributed/planner/query_pushdown_planning.c index 5ad7887e9..964acfff4 100644 --- a/src/backend/distributed/planner/query_pushdown_planning.c +++ b/src/backend/distributed/planner/query_pushdown_planning.c @@ -60,7 +60,8 @@ typedef enum RecurringTuplesType RECURRING_TUPLES_FUNCTION, RECURRING_TUPLES_EMPTY_JOIN_TREE, RECURRING_TUPLES_RESULT_FUNCTION, - RECURRING_TUPLES_VALUES + RECURRING_TUPLES_VALUES, + RECURRING_TUPLES_JSON_TABLE } RecurringTuplesType; /* @@ -345,7 +346,8 @@ IsFunctionOrValuesRTE(Node *node) RangeTblEntry *rangeTblEntry = (RangeTblEntry *) node; if (rangeTblEntry->rtekind == RTE_FUNCTION || - rangeTblEntry->rtekind == RTE_VALUES) + rangeTblEntry->rtekind == RTE_VALUES || + IsJsonTableRTE(rangeTblEntry)) { return true; } @@ -718,6 +720,13 @@ DeferErrorIfFromClauseRecurs(Query *queryTree) "the FROM clause contains VALUES", NULL, NULL); } + else if (recurType == RECURRING_TUPLES_JSON_TABLE) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "correlated subqueries are not supported when " + "the FROM clause contains JSON_TABLE", NULL, + NULL); + } /* @@ -945,6 +954,13 @@ DeferredErrorIfUnsupportedRecurringTuplesJoin( "There exist a VALUES clause in the outer " "part of the outer join", NULL); } + else if (recurType == RECURRING_TUPLES_JSON_TABLE) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "cannot pushdown the subquery", + "There exist a JSON_TABLE clause in the outer " + "part of the outer join", NULL); + } return NULL; } @@ -1235,7 +1251,8 @@ DeferErrorIfUnsupportedTableCombination(Query *queryTree) */ if (rangeTableEntry->rtekind == RTE_RELATION || rangeTableEntry->rtekind == RTE_SUBQUERY || - rangeTableEntry->rtekind == RTE_RESULT) + rangeTableEntry->rtekind == RTE_RESULT || + IsJsonTableRTE(rangeTableEntry)) /* TODO: can we have volatile???*/ { /* accepted */ } @@ -1403,6 +1420,13 @@ DeferErrorIfUnsupportedUnionQuery(Query *subqueryTree) "VALUES is not supported within a " "UNION", NULL); } + else if (recurType == RECURRING_TUPLES_JSON_TABLE) + { + return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "cannot push down this subquery", + "JSON_TABLE is not supported within a " + "UNION", NULL); + } return NULL; } @@ -1502,6 +1526,11 @@ RecurringTypeDescription(RecurringTuplesType recurType) return "a VALUES clause"; } + case RECURRING_TUPLES_JSON_TABLE: + { + return "a JSON_TABLE"; + } + case RECURRING_TUPLES_INVALID: { /* @@ -1698,7 +1727,8 @@ DeferredErrorIfUnsupportedLateralSubquery(PlannerInfo *plannerInfo, * strings anyway. */ if (recurType != RECURRING_TUPLES_VALUES && - recurType != RECURRING_TUPLES_RESULT_FUNCTION) + recurType != RECURRING_TUPLES_RESULT_FUNCTION && + recurType != RECURRING_TUPLES_JSON_TABLE) { recurTypeDescription = psprintf("%s (%s)", recurTypeDescription, recurringRangeTableEntry->eref-> @@ -1775,6 +1805,26 @@ ContainsRecurringRangeTable(List *rangeTable, RecurringTuplesType *recurType) } +/* + * IsJsonTableRTE checks whether the RTE refers to a JSON_TABLE + * table function, which was introduced in PostgreSQL 15. + */ +bool +IsJsonTableRTE(RangeTblEntry *rte) +{ +#if PG_VERSION_NUM >= PG_VERSION_15 + if (rte == NULL) + { + return false; + } + return (rte->rtekind == RTE_TABLEFUNC && + rte->tablefunc->functype == TFT_JSON_TABLE); +#endif + + return false; +} + + /* * HasRecurringTuples returns whether any part of the expression will generate * the same set of tuples in every query on shards when executing a distributed @@ -1836,6 +1886,11 @@ HasRecurringTuples(Node *node, RecurringTuplesType *recurType) *recurType = RECURRING_TUPLES_VALUES; return true; } + else if (IsJsonTableRTE(rangeTableEntry)) + { + *recurType = RECURRING_TUPLES_JSON_TABLE; + return true; + } return false; } diff --git a/src/backend/distributed/planner/recursive_planning.c b/src/backend/distributed/planner/recursive_planning.c index 17983c73c..2ebaba829 100644 --- a/src/backend/distributed/planner/recursive_planning.c +++ b/src/backend/distributed/planner/recursive_planning.c @@ -242,7 +242,7 @@ GenerateSubplansForSubqueriesAndCTEs(uint64 planId, Query *originalQuery, ereport(DEBUG1, (errmsg( "Plan " UINT64_FORMAT " query after replacing subqueries and CTEs: %s", planId, - ApplyLogRedaction(subPlanString->data)))); + subPlanString->data))); } recursivePlanningDepth--; @@ -763,7 +763,7 @@ RecursivelyPlanCTEs(Query *query, RecursivePlanningContext *planningContext) ereport(DEBUG1, (errmsg("generating subplan " UINT64_FORMAT "_%u for CTE %s: %s", planId, subPlanId, cteName, - ApplyLogRedaction(subPlanString->data)))); + subPlanString->data))); } /* build a sub plan for the CTE */ @@ -1181,7 +1181,7 @@ RecursivelyPlanSubquery(Query *subquery, RecursivePlanningContext *planningConte ereport(DEBUG1, (errmsg("generating subplan " UINT64_FORMAT "_%u for subquery %s", planId, subPlanId, - ApplyLogRedaction(subqueryString->data)))); + subqueryString->data))); } /* finally update the input subquery to point the result query */ diff --git a/src/backend/distributed/replication/multi_logical_replication.c b/src/backend/distributed/replication/multi_logical_replication.c index 64735cf19..7ee70f7f3 100644 --- a/src/backend/distributed/replication/multi_logical_replication.c +++ b/src/backend/distributed/replication/multi_logical_replication.c @@ -1807,12 +1807,22 @@ CreateSubscriptions(MultiConnection *sourceConnection, appendStringInfo(createSubscriptionCommand, "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s " "WITH (citus_use_authinfo=true, create_slot=false, " - " copy_data=false, enabled=false, slot_name=%s)", + "copy_data=false, enabled=false, slot_name=%s", quote_identifier(target->subscriptionName), quote_literal_cstr(conninfo->data), quote_identifier(target->publication->name), quote_identifier(target->replicationSlot->name)); + if (EnableBinaryProtocol && PG_VERSION_NUM >= PG_VERSION_14) + { + appendStringInfoString(createSubscriptionCommand, ", binary=true)"); + } + else + { + appendStringInfoString(createSubscriptionCommand, ")"); + } + + ExecuteCriticalRemoteCommand(target->superuserConnection, createSubscriptionCommand->data); pfree(createSubscriptionCommand->data); diff --git a/src/backend/distributed/shardsplit/shardsplit_decoder.c b/src/backend/distributed/shardsplit/shardsplit_decoder.c index f6de28ca3..59fb3118f 100644 --- a/src/backend/distributed/shardsplit/shardsplit_decoder.c +++ b/src/backend/distributed/shardsplit/shardsplit_decoder.c @@ -14,6 +14,7 @@ #include "replication/logical.h" #include "utils/typcache.h" + extern void _PG_output_plugin_init(OutputPluginCallbacks *cb); static LogicalDecodeChangeCB pgoutputChangeCB; @@ -216,8 +217,9 @@ GetHashValueForIncomingTuple(Relation sourceShardRelation, TYPECACHE_HASH_PROC_FINFO); /* get hashed value of the distribution value */ - Datum hashedValueDatum = FunctionCall1(&(typeEntry->hash_proc_finfo), - partitionColumnValue); + Datum hashedValueDatum = FunctionCall1Coll(&(typeEntry->hash_proc_finfo), + typeEntry->typcollation, + partitionColumnValue); return DatumGetInt32(hashedValueDatum); } diff --git a/src/backend/distributed/shardsplit/shardsplit_shared_memory.c b/src/backend/distributed/shardsplit/shardsplit_shared_memory.c index d9a2daad4..3e8745758 100644 --- a/src/backend/distributed/shardsplit/shardsplit_shared_memory.c +++ b/src/backend/distributed/shardsplit/shardsplit_shared_memory.c @@ -173,6 +173,11 @@ ReleaseSharedMemoryOfShardSplitInfo() /* Get handle of dynamic shared memory segment*/ dsm_handle dsmHandle = GetShardSplitSharedMemoryHandle(); + if (dsmHandle == DSM_HANDLE_INVALID) + { + return; + } + /* * Unpin the dynamic shared memory segment. 'dsm_pin_segment' was * called previously by 'AllocateSharedMemoryForShardSplitInfo'. @@ -266,8 +271,10 @@ StoreShardSplitSharedMemoryHandle(dsm_handle dsmHandle) * before the current function is called. * If this handle is still valid, it means cleanup of previous split shard * workflow failed. Log a waring and continue the current shard split operation. + * Skip warning if new handle to be stored is invalid. We store invalid handle + * when shared memory is released by calling worker_split_shard_release_dsm. */ - if (smData->dsmHandle != DSM_HANDLE_INVALID) + if (smData->dsmHandle != DSM_HANDLE_INVALID && dsmHandle != DSM_HANDLE_INVALID) { ereport(WARNING, errmsg( diff --git a/src/backend/distributed/shared_library_init.c b/src/backend/distributed/shared_library_init.c index 88d246007..224d216db 100644 --- a/src/backend/distributed/shared_library_init.c +++ b/src/backend/distributed/shared_library_init.c @@ -146,6 +146,7 @@ DEFINE_COLUMNAR_PASSTHROUGH_FUNC(test_columnar_storage_write_new_page) #define DUMMY_REAL_TIME_EXECUTOR_ENUM_VALUE 9999999 static char *CitusVersion = CITUS_VERSION; static char *DeprecatedEmptyString = ""; +static char *MitmfifoEmptyString = ""; /* deprecated GUC value that should not be used anywhere outside this file */ static int ReplicationModel = REPLICATION_MODEL_STREAMING; @@ -653,9 +654,17 @@ void StartupCitusBackend(void) { InitializeMaintenanceDaemonBackend(); - InitializeBackendData(); - RegisterConnectionCleanup(); + + /* + * For queries this will be a no-op. But for background daemons we might + * still need to initialize the backend data. For those backaground daemons + * it doesn't really matter that we temporarily assign + * INVALID_CITUS_INTERNAL_BACKEND_GPID, since we override it again two + * lines below. + */ + InitializeBackendData(INVALID_CITUS_INTERNAL_BACKEND_GPID); AssignGlobalPID(); + RegisterConnectionCleanup(); } @@ -727,8 +736,8 @@ CitusCleanupConnectionsAtExit(int code, Datum arg) DeallocateReservedConnections(); /* we don't want any monitoring view/udf to show already exited backends */ - UnSetGlobalPID(); SetActiveMyBackend(false); + UnSetGlobalPID(); } @@ -820,6 +829,26 @@ RegisterCitusConfigVariables(void) GUC_NO_SHOW_ALL, NULL, NULL, NULL); + DefineCustomBoolVariable( + "citus.allow_unsafe_constraints", + gettext_noop("Enables unique constraints and exclusion constraints " + "that do not include a distribution column."), + gettext_noop("To enforce global uniqueness, Citus normally requires " + "that unique constraints and exclusion constraints contain " + "the distribution column. If the tuple does not include the " + "distribution column, Citus cannot ensure that the same value " + "is not present in another shard. However, in some cases the " + "index creator knows that uniqueness within the shard implies " + "global uniqueness (e.g. when indexing an expression derived " + "from the distribution column) and adding the distribution column " + "separately may not be desirable. This setting can then be used " + "to disable the check."), + &AllowUnsafeConstraints, + false, + PGC_USERSET, + GUC_NO_SHOW_ALL, + NULL, NULL, NULL); + DefineCustomBoolVariable( "citus.allow_unsafe_locks_from_workers", gettext_noop("Enables acquiring a distributed lock from a worker " @@ -1786,6 +1815,24 @@ RegisterCitusConfigVariables(void) GUC_UNIT_MS | GUC_NO_SHOW_ALL, NULL, NULL, NULL); + /* + * Previously we setting this configuration parameter + * in the fly for failure tests schedule. + * However, PG15 doesn't allow that anymore: reserved prefixes + * like "citus" cannot be used to set non-existing GUCs. + * Relevant PG commit: 88103567cb8fa5be46dc9fac3e3b8774951a2be7 + */ + + DefineCustomStringVariable( + "citus.mitmfifo", + gettext_noop("Sets the citus mitm fifo path for failure tests"), + gettext_noop("This GUC is only used for testing."), + &MitmfifoEmptyString, + "", + PGC_SUSET, + GUC_NO_SHOW_ALL, + NULL, NULL, NULL); + DefineCustomEnumVariable( "citus.multi_shard_modify_mode", gettext_noop("Sets the connection type for multi shard modify queries"), @@ -2679,36 +2726,28 @@ CitusAuthHook(Port *port, int status) "regular client connections", MaxClientConnections))); } + } - /* - * Right after this, before we assign global pid, this backend - * might get blocked by a DDL as that happens during parsing. - * - * That's why, lets mark the backend as an external backend - * which is likely to execute a distributed command. - * - * We do this so that this backend gets the chance to show - * up in citus_lock_waits. - * - * We cannot assign a new global PID yet here, because that - * would require reading from catalogs, but that's not allowed - * this early in the connection startup (because no database - * has been assigned yet). - */ - InitializeBackendData(); - SetBackendDataDistributedCommandOriginator(true); - } - else - { - /* - * We set the global PID in the backend data here already to be able to - * do blocked process detection on connections that are opened over a - * replication connection. A replication connection backend will never - * call StartupCitusBackend, which normally sets up the global PID. - */ - InitializeBackendData(); - SetBackendDataGlobalPID(gpid); - } + /* + * Right after this, but before we assign global pid, this backend might + * get blocked by a DDL as that happens during parsing. + * + * That's why, we now initialize its backend data, with the gpid. + * + * We do this so that this backend gets the chance to show up in + * citus_lock_waits. + * + * We cannot assign a new global PID yet here, because that would require + * reading from catalogs, but that's not allowed this early in the + * connection startup (because no database has been assigned yet). + * + * A second reason is for backends that never call StartupCitusBackend. For + * those we already set the global PID in the backend data here to be able + * to do blocked process detection on connections that are opened over a + * replication connection. A replication connection backend will never call + * StartupCitusBackend, which normally sets up the global PID. + */ + InitializeBackendData(gpid); /* let other authentication hooks to kick in first */ if (original_client_auth_hook) diff --git a/src/backend/distributed/sql/citus--11.0-4--11.1-1.sql b/src/backend/distributed/sql/citus--11.0-4--11.1-1.sql index 2999d34a1..0a964fb2b 100644 --- a/src/backend/distributed/sql/citus--11.0-4--11.1-1.sql +++ b/src/backend/distributed/sql/citus--11.0-4--11.1-1.sql @@ -1,4 +1,6 @@ #include "udfs/citus_locks/11.1-1.sql" +#include "udfs/create_distributed_table_concurrently/11.1-1.sql" +#include "udfs/citus_internal_delete_partition_metadata/11.1-1.sql" DROP FUNCTION pg_catalog.worker_create_schema(bigint,text); DROP FUNCTION pg_catalog.worker_cleanup_job_schema_cache(); @@ -73,6 +75,7 @@ DROP FUNCTION pg_catalog.get_all_active_transactions(OUT datid oid, OUT process_ #include "udfs/worker_split_shard_replication_setup/11.1-1.sql" #include "udfs/citus_isolation_test_session_is_blocked/11.1-1.sql" #include "udfs/replicate_reference_tables/11.1-1.sql" +#include "udfs/worker_split_shard_release_dsm/11.1-1.sql" DROP FUNCTION pg_catalog.isolate_tenant_to_new_shard(table_name regclass, tenant_id "any", cascade_option text); #include "udfs/isolate_tenant_to_new_shard/11.1-1.sql" diff --git a/src/backend/distributed/sql/downgrades/citus--11.1-1--11.0-4.sql b/src/backend/distributed/sql/downgrades/citus--11.1-1--11.0-4.sql index d80cb79b6..1aef756fd 100644 --- a/src/backend/distributed/sql/downgrades/citus--11.1-1--11.0-4.sql +++ b/src/backend/distributed/sql/downgrades/citus--11.1-1--11.0-4.sql @@ -70,6 +70,7 @@ DROP FUNCTION pg_catalog.citus_split_shard_by_split_points( shard_transfer_mode citus.shard_transfer_mode); DROP FUNCTION pg_catalog.worker_split_copy( source_shard_id bigint, + distribution_column text, splitCopyInfos pg_catalog.split_copy_info[]); DROP TYPE pg_catalog.split_copy_info; @@ -82,6 +83,8 @@ DROP FUNCTION pg_catalog.worker_split_shard_replication_setup( DROP TYPE pg_catalog.split_shard_info; DROP TYPE pg_catalog.replication_slot_info; +DROP FUNCTION pg_catalog.worker_split_shard_release_dsm(); + DROP FUNCTION pg_catalog.get_all_active_transactions(OUT datid oid, OUT process_id int, OUT initiator_node_identifier int4, OUT worker_query BOOL, OUT transaction_number int8, OUT transaction_stamp timestamptz, OUT global_pid int8); @@ -95,6 +98,8 @@ DROP FUNCTION pg_catalog.replicate_reference_tables(citus.shard_transfer_mode); DROP FUNCTION pg_catalog.isolate_tenant_to_new_shard(table_name regclass, tenant_id "any", cascade_option text, shard_transfer_mode citus.shard_transfer_mode); #include "../udfs/isolate_tenant_to_new_shard/8.0-1.sql" +DROP FUNCTION pg_catalog.create_distributed_table_concurrently; +DROP FUNCTION pg_catalog.citus_internal_delete_partition_metadata(regclass); DROP TABLE pg_catalog.pg_dist_cleanup; DROP SEQUENCE pg_catalog.pg_dist_operationid_seq; diff --git a/src/backend/distributed/sql/udfs/citus_internal_delete_partition_metadata/11.1-1.sql b/src/backend/distributed/sql/udfs/citus_internal_delete_partition_metadata/11.1-1.sql new file mode 100644 index 000000000..c7cb5455d --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_delete_partition_metadata/11.1-1.sql @@ -0,0 +1,7 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_delete_partition_metadata(table_name regclass) + RETURNS void + LANGUAGE C STRICT + AS 'MODULE_PATHNAME'; +COMMENT ON FUNCTION pg_catalog.citus_internal_delete_partition_metadata(regclass) IS + 'Deletes a row from pg_dist_partition with table ownership checks'; + diff --git a/src/backend/distributed/sql/udfs/citus_internal_delete_partition_metadata/latest.sql b/src/backend/distributed/sql/udfs/citus_internal_delete_partition_metadata/latest.sql new file mode 100644 index 000000000..c7cb5455d --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_internal_delete_partition_metadata/latest.sql @@ -0,0 +1,7 @@ +CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_delete_partition_metadata(table_name regclass) + RETURNS void + LANGUAGE C STRICT + AS 'MODULE_PATHNAME'; +COMMENT ON FUNCTION pg_catalog.citus_internal_delete_partition_metadata(regclass) IS + 'Deletes a row from pg_dist_partition with table ownership checks'; + diff --git a/src/backend/distributed/sql/udfs/create_distributed_table_concurrently/11.1-1.sql b/src/backend/distributed/sql/udfs/create_distributed_table_concurrently/11.1-1.sql new file mode 100644 index 000000000..721f44990 --- /dev/null +++ b/src/backend/distributed/sql/udfs/create_distributed_table_concurrently/11.1-1.sql @@ -0,0 +1,14 @@ +CREATE FUNCTION pg_catalog.create_distributed_table_concurrently(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 C + AS 'MODULE_PATHNAME', $$create_distributed_table_concurrently$$; +COMMENT ON FUNCTION pg_catalog.create_distributed_table_concurrently(table_name regclass, + distribution_column text, + distribution_type citus.distribution_type, + colocate_with text, + shard_count int) + IS 'creates a distributed table and avoids blocking writes'; diff --git a/src/backend/distributed/sql/udfs/create_distributed_table_concurrently/latest.sql b/src/backend/distributed/sql/udfs/create_distributed_table_concurrently/latest.sql new file mode 100644 index 000000000..721f44990 --- /dev/null +++ b/src/backend/distributed/sql/udfs/create_distributed_table_concurrently/latest.sql @@ -0,0 +1,14 @@ +CREATE FUNCTION pg_catalog.create_distributed_table_concurrently(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 C + AS 'MODULE_PATHNAME', $$create_distributed_table_concurrently$$; +COMMENT ON FUNCTION pg_catalog.create_distributed_table_concurrently(table_name regclass, + distribution_column text, + distribution_type citus.distribution_type, + colocate_with text, + shard_count int) + IS 'creates a distributed table and avoids blocking writes'; diff --git a/src/backend/distributed/sql/udfs/worker_split_copy/11.1-1.sql b/src/backend/distributed/sql/udfs/worker_split_copy/11.1-1.sql index 0ecad4a07..d375af964 100644 --- a/src/backend/distributed/sql/udfs/worker_split_copy/11.1-1.sql +++ b/src/backend/distributed/sql/udfs/worker_split_copy/11.1-1.sql @@ -14,9 +14,10 @@ ALTER TYPE citus.split_copy_info SET SCHEMA pg_catalog; CREATE OR REPLACE FUNCTION pg_catalog.worker_split_copy( source_shard_id bigint, + distribution_column text, splitCopyInfos pg_catalog.split_copy_info[]) RETURNS void LANGUAGE C STRICT AS 'MODULE_PATHNAME', $$worker_split_copy$$; -COMMENT ON FUNCTION pg_catalog.worker_split_copy(source_shard_id bigint, splitCopyInfos pg_catalog.split_copy_info[]) +COMMENT ON FUNCTION pg_catalog.worker_split_copy(source_shard_id bigint, distribution_column text, splitCopyInfos pg_catalog.split_copy_info[]) IS 'Perform split copy for shard'; diff --git a/src/backend/distributed/sql/udfs/worker_split_copy/latest.sql b/src/backend/distributed/sql/udfs/worker_split_copy/latest.sql index 0ecad4a07..d375af964 100644 --- a/src/backend/distributed/sql/udfs/worker_split_copy/latest.sql +++ b/src/backend/distributed/sql/udfs/worker_split_copy/latest.sql @@ -14,9 +14,10 @@ ALTER TYPE citus.split_copy_info SET SCHEMA pg_catalog; CREATE OR REPLACE FUNCTION pg_catalog.worker_split_copy( source_shard_id bigint, + distribution_column text, splitCopyInfos pg_catalog.split_copy_info[]) RETURNS void LANGUAGE C STRICT AS 'MODULE_PATHNAME', $$worker_split_copy$$; -COMMENT ON FUNCTION pg_catalog.worker_split_copy(source_shard_id bigint, splitCopyInfos pg_catalog.split_copy_info[]) +COMMENT ON FUNCTION pg_catalog.worker_split_copy(source_shard_id bigint, distribution_column text, splitCopyInfos pg_catalog.split_copy_info[]) IS 'Perform split copy for shard'; diff --git a/src/backend/distributed/sql/udfs/worker_split_shard_release_dsm/11.1-1.sql b/src/backend/distributed/sql/udfs/worker_split_shard_release_dsm/11.1-1.sql new file mode 100644 index 000000000..5df6554a1 --- /dev/null +++ b/src/backend/distributed/sql/udfs/worker_split_shard_release_dsm/11.1-1.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE FUNCTION pg_catalog.worker_split_shard_release_dsm() +RETURNS void +LANGUAGE C STRICT +AS 'MODULE_PATHNAME', $$worker_split_shard_release_dsm$$; +COMMENT ON FUNCTION pg_catalog.worker_split_shard_release_dsm() + IS 'Releases shared memory segment allocated by non-blocking split workflow'; + +REVOKE ALL ON FUNCTION pg_catalog.worker_split_shard_release_dsm() FROM PUBLIC; diff --git a/src/backend/distributed/sql/udfs/worker_split_shard_release_dsm/latest.sql b/src/backend/distributed/sql/udfs/worker_split_shard_release_dsm/latest.sql new file mode 100644 index 000000000..5df6554a1 --- /dev/null +++ b/src/backend/distributed/sql/udfs/worker_split_shard_release_dsm/latest.sql @@ -0,0 +1,8 @@ +CREATE OR REPLACE FUNCTION pg_catalog.worker_split_shard_release_dsm() +RETURNS void +LANGUAGE C STRICT +AS 'MODULE_PATHNAME', $$worker_split_shard_release_dsm$$; +COMMENT ON FUNCTION pg_catalog.worker_split_shard_release_dsm() + IS 'Releases shared memory segment allocated by non-blocking split workflow'; + +REVOKE ALL ON FUNCTION pg_catalog.worker_split_shard_release_dsm() FROM PUBLIC; diff --git a/src/backend/distributed/transaction/backend_data.c b/src/backend/distributed/transaction/backend_data.c index 5d16b92b7..34d90969b 100644 --- a/src/backend/distributed/transaction/backend_data.c +++ b/src/backend/distributed/transaction/backend_data.c @@ -671,13 +671,17 @@ TotalProcCount(void) * the Citus extension is present, and after any subsequent invalidation of * pg_dist_partition (see InvalidateMetadataSystemCache()). * - * We only need to initialise MyBackendData once. The only goal here is to make + * We only need to initialise MyBackendData once. The main goal here is to make * sure that we don't use the backend data from a previous backend with the same * pgprocno. Resetting the backend data after a distributed transaction happens * on COMMIT/ABORT through transaction callbacks. + * + * We do also initialize the distributedCommandOriginator and globalPID values + * based on these values. This is to make sure that once the backend date is + * initialized this backend can be correctly shown in citus_lock_waits. */ void -InitializeBackendData(void) +InitializeBackendData(uint64 globalPID) { if (MyBackendData != NULL) { @@ -695,10 +699,27 @@ InitializeBackendData(void) LockBackendSharedMemory(LW_EXCLUSIVE); - /* zero out the backend data */ + /* zero out the backend its transaction id */ UnSetDistributedTransactionId(); UnSetGlobalPID(); + SpinLockAcquire(&MyBackendData->mutex); + + /* + * Use the given globalPID to initialize + */ + if (globalPID == INVALID_CITUS_INTERNAL_BACKEND_GPID) + { + MyBackendData->distributedCommandOriginator = + true; + } + else + { + MyBackendData->globalPID = globalPID; + MyBackendData->distributedCommandOriginator = false; + } + SpinLockRelease(&MyBackendData->mutex); + /* * Signal that this backend is active and should show up * on activity monitors. @@ -893,23 +914,6 @@ AssignGlobalPID(void) } -/* - * SetBackendDataGlobalPID sets the global PID. This specifically does not read - * from catalog tables, because it should be safe to run from our - * authentication hook. - */ -void -SetBackendDataGlobalPID(uint64 globalPID) -{ - SpinLockAcquire(&MyBackendData->mutex); - - MyBackendData->globalPID = globalPID; - MyBackendData->distributedCommandOriginator = false; - - SpinLockRelease(&MyBackendData->mutex); -} - - /* * SetBackendDataDistributedCommandOriginator is used to set the distributedCommandOriginator * field on MyBackendData. diff --git a/src/backend/distributed/transaction/distributed_deadlock_detection.c b/src/backend/distributed/transaction/distributed_deadlock_detection.c index 5219336a3..cf8dd43f5 100644 --- a/src/backend/distributed/transaction/distributed_deadlock_detection.c +++ b/src/backend/distributed/transaction/distributed_deadlock_detection.c @@ -657,7 +657,7 @@ LogDistributedDeadlockDebugMessage(const char *errorMessage) } ereport(LOG, (errmsg("[%s] %s", timestamptz_to_str(GetCurrentTimestamp()), - ApplyLogRedaction(errorMessage)))); + errorMessage))); } diff --git a/src/backend/distributed/transaction/worker_transaction.c b/src/backend/distributed/transaction/worker_transaction.c index 6b4b1a351..8cce25aca 100644 --- a/src/backend/distributed/transaction/worker_transaction.c +++ b/src/backend/distributed/transaction/worker_transaction.c @@ -44,13 +44,7 @@ static void SendCommandToWorkersParamsInternal(TargetWorkerSet targetWorkerSet, const Oid *parameterTypes, const char *const *parameterValues); static void ErrorIfAnyMetadataNodeOutOfSync(List *metadataNodeList); -static List * OpenConnectionsToWorkersInParallel(TargetWorkerSet targetWorkerSet, - const char *user); -static void GetConnectionsResults(List *connectionList, bool failOnError); -static void SendCommandToWorkersOutsideTransaction(TargetWorkerSet targetWorkerSet, - const char *command, const char *user, - bool - failOnError); + /* * SendCommandToWorker sends a command to a particular worker as part of the @@ -238,129 +232,6 @@ SendCommandToMetadataWorkersParams(const char *command, } -/* - * SendCommandToWorkersOptionalInParallel sends the given command to workers in parallel. - * It does error if there is a problem while sending the query, but it doesn't error - * if there is a problem while executing the query. - */ -void -SendCommandToWorkersOptionalInParallel(TargetWorkerSet targetWorkerSet, const - char *command, - const char *user) -{ - bool failOnError = false; - SendCommandToWorkersOutsideTransaction(targetWorkerSet, command, user, - failOnError); -} - - -/* - * SendCommandToWorkersInParallel sends the given command to workers in parallel. - * It does error if there is a problem while sending the query, it errors if there - * was any problem when sending/receiving. - */ -void -SendCommandToWorkersInParallel(TargetWorkerSet targetWorkerSet, const - char *command, - const char *user) -{ - bool failOnError = true; - SendCommandToWorkersOutsideTransaction(targetWorkerSet, command, user, - failOnError); -} - - -/* - * SendCommandToWorkersOutsideTransaction sends the given command to workers in parallel. - */ -static void -SendCommandToWorkersOutsideTransaction(TargetWorkerSet targetWorkerSet, const - char *command, const char *user, bool - failOnError) -{ - List *connectionList = OpenConnectionsToWorkersInParallel(targetWorkerSet, user); - - /* finish opening connections */ - FinishConnectionListEstablishment(connectionList); - - /* send commands in parallel */ - MultiConnection *connection = NULL; - foreach_ptr(connection, connectionList) - { - int querySent = SendRemoteCommand(connection, command); - if (failOnError && querySent == 0) - { - ReportConnectionError(connection, ERROR); - } - } - - GetConnectionsResults(connectionList, failOnError); -} - - -/* - * OpenConnectionsToWorkersInParallel opens connections to the given target worker set in parallel, - * as the given user. - */ -static List * -OpenConnectionsToWorkersInParallel(TargetWorkerSet targetWorkerSet, const char *user) -{ - List *connectionList = NIL; - - List *workerNodeList = TargetWorkerSetNodeList(targetWorkerSet, RowShareLock); - - WorkerNode *workerNode = NULL; - foreach_ptr(workerNode, workerNodeList) - { - const char *nodeName = workerNode->workerName; - int nodePort = workerNode->workerPort; - int32 connectionFlags = OUTSIDE_TRANSACTION; - - MultiConnection *connection = StartNodeUserDatabaseConnection(connectionFlags, - nodeName, nodePort, - user, NULL); - - /* - * connection can only be NULL for optional connections, which we don't - * support in this codepath. - */ - Assert((connectionFlags & OPTIONAL_CONNECTION) == 0); - Assert(connection != NULL); - connectionList = lappend(connectionList, connection); - } - return connectionList; -} - - -/* - * GetConnectionsResults gets remote command results - * for the given connections. It raises any error if failOnError is true. - */ -static void -GetConnectionsResults(List *connectionList, bool failOnError) -{ - MultiConnection *connection = NULL; - foreach_ptr(connection, connectionList) - { - bool raiseInterrupt = false; - PGresult *result = GetRemoteCommandResult(connection, raiseInterrupt); - - bool isResponseOK = result != NULL && IsResponseOK(result); - if (failOnError && !isResponseOK) - { - ReportResultError(connection, result, ERROR); - } - - PQclear(result); - - if (isResponseOK) - { - ForgetResults(connection); - } - } -} - - /* * SendCommandToWorkersParamsInternal sends a command to all workers in parallel. * Commands are committed on the workers when the local transaction commits. The diff --git a/src/backend/distributed/utils/array_type.c b/src/backend/distributed/utils/array_type.c index b214d2ee7..348b25b4a 100644 --- a/src/backend/distributed/utils/array_type.c +++ b/src/backend/distributed/utils/array_type.c @@ -114,7 +114,8 @@ IntegerArrayTypeToList(ArrayType *arrayObject) for (int index = 0; index < arrayObjectCount; index++) { - list = lappend_int(list, datumObjectArray[index]); + int32 intObject = DatumGetInt32(datumObjectArray[index]); + list = lappend_int(list, intObject); } return list; diff --git a/src/backend/distributed/utils/citus_copyfuncs.c b/src/backend/distributed/utils/citus_copyfuncs.c index 605732b65..1e7f5f02b 100644 --- a/src/backend/distributed/utils/citus_copyfuncs.c +++ b/src/backend/distributed/utils/citus_copyfuncs.c @@ -198,7 +198,6 @@ CopyNodeMapMergeJob(COPYFUNC_ARGS) copyJobInfo(&newnode->job, &from->job); - COPY_NODE_FIELD(reduceQuery); COPY_SCALAR_FIELD(partitionType); COPY_NODE_FIELD(partitionColumn); COPY_SCALAR_FIELD(partitionCount); diff --git a/src/backend/distributed/utils/citus_outfuncs.c b/src/backend/distributed/utils/citus_outfuncs.c index c0748ede7..8c53ca103 100644 --- a/src/backend/distributed/utils/citus_outfuncs.c +++ b/src/backend/distributed/utils/citus_outfuncs.c @@ -401,7 +401,6 @@ OutMapMergeJob(OUTFUNC_ARGS) WRITE_NODE_TYPE("MAPMERGEJOB"); OutJobFields(str, (Job *) node); - WRITE_NODE_FIELD(reduceQuery); WRITE_ENUM_FIELD(partitionType, PartitionType); WRITE_NODE_FIELD(partitionColumn); WRITE_UINT_FIELD(partitionCount); diff --git a/src/backend/distributed/utils/colocation_utils.c b/src/backend/distributed/utils/colocation_utils.c index 005f3aa62..bb9488af1 100644 --- a/src/backend/distributed/utils/colocation_utils.c +++ b/src/backend/distributed/utils/colocation_utils.c @@ -53,6 +53,7 @@ static void DeleteColocationGroup(uint32 colocationId); static uint32 CreateColocationGroupForRelation(Oid sourceRelationId); static void BreakColocation(Oid sourceRelationId); + /* exports for SQL callable functions */ PG_FUNCTION_INFO_V1(mark_tables_colocated); PG_FUNCTION_INFO_V1(get_colocated_shard_array); @@ -142,6 +143,17 @@ IsColocateWithNone(char *colocateWithTableName) } +/* + * IsColocateWithDefault returns true if the given table is + * the special keyword "default". + */ +bool +IsColocateWithDefault(char *colocateWithTableName) +{ + return pg_strncasecmp(colocateWithTableName, "default", NAMEDATALEN) == 0; +} + + /* * BreakColocation breaks the colocations of the given relation id. * If t1, t2 and t3 are colocated and we call this function with t2, @@ -564,6 +576,39 @@ ColocationId(int shardCount, int replicationFactor, Oid distributionColumnType, } +/* + * AcquireColocationDefaultLock serializes concurrent creation of a colocation entry + * for default group. + */ +void +AcquireColocationDefaultLock(void) +{ + LOCKTAG tag; + const bool sessionLock = false; + const bool dontWait = false; + + SET_LOCKTAG_CITUS_OPERATION(tag, CITUS_CREATE_COLOCATION_DEFAULT); + + (void) LockAcquire(&tag, ExclusiveLock, sessionLock, dontWait); +} + + +/* + * ReleaseColocationDefaultLock releases the lock for concurrent creation of a colocation entry + * for default group. + */ +void +ReleaseColocationDefaultLock(void) +{ + LOCKTAG tag; + const bool sessionLock = false; + + SET_LOCKTAG_CITUS_OPERATION(tag, CITUS_CREATE_COLOCATION_DEFAULT); + + LockRelease(&tag, ExclusiveLock, sessionLock); +} + + /* * CreateColocationGroup creates a new colocation id and writes it into * pg_dist_colocation with the given configuration. It also returns the created @@ -619,7 +664,7 @@ InsertColocationGroupLocally(uint32 colocationId, int shardCount, int replicatio /* increment the counter so that next command can see the row */ CommandCounterIncrement(); - table_close(pgDistColocation, RowExclusiveLock); + table_close(pgDistColocation, NoLock); } @@ -1271,5 +1316,110 @@ DeleteColocationGroupLocally(uint32 colocationId) } systable_endscan(scanDescriptor); - table_close(pgDistColocation, RowExclusiveLock); + table_close(pgDistColocation, NoLock); +} + + +/* + * FindColocateWithColocationId tries to find a colocation ID for a given + * colocate_with clause passed to create_distributed_table. + */ +uint32 +FindColocateWithColocationId(Oid relationId, char replicationModel, + Oid distributionColumnType, + Oid distributionColumnCollation, + int shardCount, bool shardCountIsStrict, + char *colocateWithTableName) +{ + uint32 colocationId = INVALID_COLOCATION_ID; + + if (IsColocateWithDefault(colocateWithTableName)) + { + /* check for default colocation group */ + colocationId = ColocationId(shardCount, ShardReplicationFactor, + distributionColumnType, + distributionColumnCollation); + + /* + * if the shardCount is strict then we check if the shard count + * of the colocated table is actually shardCount + */ + if (shardCountIsStrict && colocationId != INVALID_COLOCATION_ID) + { + Oid colocatedTableId = ColocatedTableId(colocationId); + + if (colocatedTableId != InvalidOid) + { + CitusTableCacheEntry *cacheEntry = + GetCitusTableCacheEntry(colocatedTableId); + int colocatedTableShardCount = cacheEntry->shardIntervalArrayLength; + + if (colocatedTableShardCount != shardCount) + { + colocationId = INVALID_COLOCATION_ID; + } + } + } + } + else if (!IsColocateWithNone(colocateWithTableName)) + { + text *colocateWithTableNameText = cstring_to_text(colocateWithTableName); + Oid sourceRelationId = ResolveRelationId(colocateWithTableNameText, false); + + EnsureTableCanBeColocatedWith(relationId, replicationModel, + distributionColumnType, sourceRelationId); + + colocationId = TableColocationId(sourceRelationId); + } + + return colocationId; +} + + +/* + * EnsureTableCanBeColocatedWith checks whether a given replication model and + * distribution column type is suitable to distribute a table to be colocated + * with given source table. + * + * We only pass relationId to provide meaningful error messages. + */ +void +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)) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot distribute relation"), + errdetail("Currently, colocate_with option is only supported " + "for hash distributed tables."))); + } + + if (sourceReplicationModel != replicationModel) + { + char *relationName = get_rel_name(relationId); + char *sourceRelationName = get_rel_name(sourceRelationId); + + ereport(ERROR, (errmsg("cannot colocate tables %s and %s", + sourceRelationName, relationName), + errdetail("Replication models don't match for %s and %s.", + sourceRelationName, relationName))); + } + + Oid sourceDistributionColumnType = sourceDistributionColumn->vartype; + if (sourceDistributionColumnType != distributionColumnType) + { + char *relationName = get_rel_name(relationId); + char *sourceRelationName = get_rel_name(sourceRelationId); + + ereport(ERROR, (errmsg("cannot colocate tables %s and %s", + sourceRelationName, relationName), + errdetail("Distribution column types don't match for " + "%s and %s.", sourceRelationName, + relationName))); + } } diff --git a/src/backend/distributed/utils/directory.c b/src/backend/distributed/utils/directory.c index 4797c50ee..b749b9cd6 100644 --- a/src/backend/distributed/utils/directory.c +++ b/src/backend/distributed/utils/directory.c @@ -22,35 +22,6 @@ static bool FileIsLink(const char *filename, struct stat filestat); -/* - * CacheDirectoryElement takes in a filename, and checks if this name lives in - * the directory path that is used for job, task, table etc. files. - */ -bool -CacheDirectoryElement(const char *filename) -{ - bool directoryElement = false; - - StringInfo directoryPath = makeStringInfo(); - appendStringInfo(directoryPath, "base/%s/", PG_JOB_CACHE_DIR); - - char *directoryPathFound = strstr(filename, directoryPath->data); - - /* - * If directoryPath occurs at the beginning of the filename, then the - * pointers should now be equal. - */ - if (directoryPathFound == filename) - { - directoryElement = true; - } - - pfree(directoryPath); - - return directoryElement; -} - - /* * CitusCreateDirectory creates a new directory with the given directory name. */ diff --git a/src/backend/distributed/utils/distribution_column.c b/src/backend/distributed/utils/distribution_column.c index 5b59bd529..7fbab98eb 100644 --- a/src/backend/distributed/utils/distribution_column.c +++ b/src/backend/distributed/utils/distribution_column.c @@ -24,6 +24,7 @@ #include "nodes/nodes.h" #include "nodes/primnodes.h" #include "parser/scansup.h" +#include "parser/parse_relation.h" #include "utils/builtins.h" #include "utils/elog.h" #include "utils/errcodes.h" @@ -123,7 +124,7 @@ column_to_column_name(PG_FUNCTION_ARGS) Var * BuildDistributionKeyFromColumnName(Oid relationId, char *columnName, LOCKMODE lockMode) { - Relation relation = try_relation_open(relationId, ExclusiveLock); + Relation relation = try_relation_open(relationId, lockMode); if (relation == NULL) { @@ -172,6 +173,76 @@ BuildDistributionKeyFromColumnName(Oid relationId, char *columnName, LOCKMODE lo } +/* + * EnsureValidDistributionColumn Errors out if the + * specified column does not exist or is not suitable to be used as a + * distribution column. It does not hold locks. + */ +void +EnsureValidDistributionColumn(Oid relationId, char *columnName) +{ + Relation relation = try_relation_open(relationId, AccessShareLock); + + if (relation == NULL) + { + ereport(ERROR, (errmsg("relation does not exist"))); + } + + char *tableName = get_rel_name(relationId); + + /* it'd probably better to downcase identifiers consistent with SQL case folding */ + truncate_identifier(columnName, strlen(columnName), true); + + /* lookup column definition */ + HeapTuple columnTuple = SearchSysCacheAttName(relationId, columnName); + if (!HeapTupleIsValid(columnTuple)) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + columnName, tableName))); + } + + Form_pg_attribute columnForm = (Form_pg_attribute) GETSTRUCT(columnTuple); + + /* check if the column may be referenced in the distribution key */ + if (columnForm->attnum <= 0) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reference system column \"%s\" in relation \"%s\"", + columnName, tableName))); + } + + ReleaseSysCache(columnTuple); + + relation_close(relation, AccessShareLock); +} + + +/* + * ColumnTypeIdForRelationColumnName returns type id for the given relation's column name. + */ +Oid +ColumnTypeIdForRelationColumnName(Oid relationId, char *columnName) +{ + Assert(columnName != NULL); + + AttrNumber attNum = get_attnum(relationId, columnName); + + if (attNum == InvalidAttrNumber) + { + ereport(ERROR, (errmsg("invalid attr %s", columnName))); + } + + Relation relation = relation_open(relationId, AccessShareLock); + + Oid typeId = attnumTypeId(relation, attNum); + + relation_close(relation, AccessShareLock); + + return typeId; +} + + /* * ColumnToColumnName returns the human-readable name of a column given a * relation identifier and the column's internal (Var) representation. diff --git a/src/backend/distributed/utils/distribution_column_map.c b/src/backend/distributed/utils/distribution_column_map.c new file mode 100644 index 000000000..c3c0db01f --- /dev/null +++ b/src/backend/distributed/utils/distribution_column_map.c @@ -0,0 +1,139 @@ +/*------------------------------------------------------------------------- + * + * distribution_column_map.c + * Implementation of a relation OID to distribution column map. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "common/hashfn.h" +#include "distributed/distribution_column.h" +#include "distributed/listutils.h" +#include "distributed/multi_join_order.h" +#include "distributed/multi_partitioning_utils.h" +#include "distributed/utils/distribution_column_map.h" +#include "nodes/primnodes.h" + + +/* + * RelationIdDistributionColumnMapEntry is used to map relation IDs to + * distribution column Vars. + */ +typedef struct RelationIdDistributionColumnMapEntry +{ + /* OID of the relation */ + Oid relationId; + + /* a Var describing the distribution column */ + Var *distributionColumn; +} RelationIdDistributionColumnMapEntry; + + +/* + * CreateDistributionColumnMap creates an empty (OID -> distribution column Var) map. + */ +DistributionColumnMap * +CreateDistributionColumnMap(void) +{ + HASHCTL info = { 0 }; + info.keysize = sizeof(Oid); + info.entrysize = sizeof(RelationIdDistributionColumnMapEntry); + info.hash = oid_hash; + info.hcxt = CurrentMemoryContext; + + uint32 hashFlags = (HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + HTAB *distributionColumnMap = hash_create("Distribution Column Map", 32, + &info, hashFlags); + + return distributionColumnMap; +} + + +/* + * AddDistributionColumnForRelation adds the given OID and its distribution column + * to the hash, as well as any child partitions. + */ +void +AddDistributionColumnForRelation(DistributionColumnMap *distributionColumnMap, + Oid relationId, + char *distributionColumnName) +{ + bool entryFound = false; + RelationIdDistributionColumnMapEntry *entry = + hash_search(distributionColumnMap, &relationId, HASH_ENTER, &entryFound); + + Assert(!entryFound); + + entry->distributionColumn = + BuildDistributionKeyFromColumnName(relationId, distributionColumnName, NoLock); + + if (PartitionedTable(relationId)) + { + /* + * Recursively add partitions as well. + */ + List *partitionList = PartitionList(relationId); + Oid partitionRelationId = InvalidOid; + + foreach_oid(partitionRelationId, partitionList) + { + AddDistributionColumnForRelation(distributionColumnMap, partitionRelationId, + distributionColumnName); + } + } +} + + +/* + * GetDistributionColumnFromMap returns the distribution column for a given + * relation ID from the distribution column map. + */ +Var * +GetDistributionColumnFromMap(DistributionColumnMap *distributionColumnMap, + Oid relationId) +{ + bool entryFound = false; + + RelationIdDistributionColumnMapEntry *entry = + hash_search(distributionColumnMap, &relationId, HASH_FIND, &entryFound); + + if (entryFound) + { + return entry->distributionColumn; + } + else + { + return NULL; + } +} + + +/* + * GetDistributionColumnWithOverrides returns the distribution column for a given + * relation from the distribution column overrides map, or the metadata if no + * override is specified. + */ +Var * +GetDistributionColumnWithOverrides(Oid relationId, + DistributionColumnMap *distributionColumnOverrides) +{ + Var *distributionColumn = NULL; + + if (distributionColumnOverrides != NULL) + { + distributionColumn = GetDistributionColumnFromMap(distributionColumnOverrides, + relationId); + if (distributionColumn != NULL) + { + return distributionColumn; + } + } + + /* no override defined, use distribution column from metadata */ + return DistPartitionKey(relationId); +} diff --git a/src/backend/distributed/utils/enable_ssl.c b/src/backend/distributed/utils/enable_ssl.c index 0dca1d0fa..28f8026fa 100644 --- a/src/backend/distributed/utils/enable_ssl.c +++ b/src/backend/distributed/utils/enable_ssl.c @@ -8,6 +8,19 @@ *------------------------------------------------------------------------- */ + +/* + * Make sure that functions marked as deprecated in OpenSSL 3.0 don't trigger + * deprecation warnings by indicating that we're using the OpenSSL 1.0.1 + * compatibile API. Postgres does this by already in PG14, so we should not do + * it otherwise we get warnings about redefining this value. + */ +#if PG_VERSION_NUM < PG_VERSION_14 +#ifndef OPENSSL_API_COMPAT +#define OPENSSL_API_COMPAT 0x1000100L +#endif +#endif + #include "postgres.h" #include "distributed/connection_management.h" diff --git a/src/backend/distributed/utils/log_utils.c b/src/backend/distributed/utils/log_utils.c index 97c8a6976..ed463c40a 100644 --- a/src/backend/distributed/utils/log_utils.c +++ b/src/backend/distributed/utils/log_utils.c @@ -39,14 +39,3 @@ IsLoggableLevel(int logLevel) { return log_min_messages <= logLevel || client_min_messages <= logLevel; } - - -/* - * HashLogMessage is deprecated and doesn't do anything anymore. Its indirect - * usage will be removed later. - */ -char * -HashLogMessage(const char *logText) -{ - return (char *) logText; -} diff --git a/src/backend/distributed/utils/multi_partitioning_utils.c b/src/backend/distributed/utils/multi_partitioning_utils.c index 3ec02da48..c5fcd2377 100644 --- a/src/backend/distributed/utils/multi_partitioning_utils.c +++ b/src/backend/distributed/utils/multi_partitioning_utils.c @@ -996,11 +996,19 @@ IsParentTable(Oid relationId) systable_endscan(scan); table_close(pgInherits, AccessShareLock); - if (tableInherited && PartitionedTable(relationId)) + Relation relation = try_relation_open(relationId, AccessShareLock); + if (relation == NULL) + { + ereport(ERROR, (errmsg("relation with OID %u does not exist", relationId))); + } + + if (tableInherited && PartitionedTableNoLock(relationId)) { tableInherited = false; } + relation_close(relation, AccessShareLock); + return tableInherited; } @@ -1291,3 +1299,29 @@ PartitionBound(Oid partitionId) return partitionBoundString; } + + +/* + * ListShardsUnderParentRelation returns a list of ShardInterval for every + * shard under a given relation, meaning it includes the shards of child + * tables in a partitioning hierarchy. + */ +List * +ListShardsUnderParentRelation(Oid relationId) +{ + List *shardList = LoadShardIntervalList(relationId); + + if (PartitionedTable(relationId)) + { + List *partitionList = PartitionList(relationId); + Oid partitionRelationId = InvalidOid; + + foreach_oid(partitionRelationId, partitionList) + { + List *childShardList = ListShardsUnderParentRelation(partitionRelationId); + shardList = list_concat(shardList, childShardList); + } + } + + return shardList; +} diff --git a/src/backend/distributed/utils/reference_table_utils.c b/src/backend/distributed/utils/reference_table_utils.c index 43b89f7a7..23e608a18 100644 --- a/src/backend/distributed/utils/reference_table_utils.c +++ b/src/backend/distributed/utils/reference_table_utils.c @@ -108,7 +108,13 @@ EnsureReferenceTablesExistOnAllNodesExtended(char transferMode) uint64 shardId = INVALID_SHARD_ID; List *newWorkersList = NIL; const char *referenceTableName = NULL; - int colocationId = CreateReferenceTableColocationId(); + int colocationId = GetReferenceTableColocationId(); + + if (colocationId == INVALID_COLOCATION_ID) + { + /* we have no reference table yet. */ + return; + } /* * Most of the time this function should result in a conclusion where we do not need diff --git a/src/backend/distributed/worker/worker_data_fetch_protocol.c b/src/backend/distributed/worker/worker_data_fetch_protocol.c index cbc7af89a..f7dd8e489 100644 --- a/src/backend/distributed/worker/worker_data_fetch_protocol.c +++ b/src/backend/distributed/worker/worker_data_fetch_protocol.c @@ -431,7 +431,7 @@ ParseTreeRawStmt(const char *ddlCommand) /* log immediately if dictated by log statement */ if (check_log_statement(parseTreeList)) { - ereport(LOG, (errmsg("statement: %s", ApplyLogRedaction(ddlCommand)), + ereport(LOG, (errmsg("statement: %s", ddlCommand), errhidestmt(true))); } diff --git a/src/include/distributed/backend_data.h b/src/include/distributed/backend_data.h index 6ba294604..12dbfb6df 100644 --- a/src/include/distributed/backend_data.h +++ b/src/include/distributed/backend_data.h @@ -50,7 +50,7 @@ extern void BackendManagementShmemInit(void); extern size_t BackendManagementShmemSize(void); extern void InitializeBackendManagement(void); extern int TotalProcCount(void); -extern void InitializeBackendData(void); +extern void InitializeBackendData(uint64 globalPID); extern void LockBackendSharedMemory(LWLockMode lockMode); extern void UnlockBackendSharedMemory(void); extern void UnSetDistributedTransactionId(void); diff --git a/src/include/distributed/colocation_utils.h b/src/include/distributed/colocation_utils.h index 0095ac427..9e6641cd3 100644 --- a/src/include/distributed/colocation_utils.h +++ b/src/include/distributed/colocation_utils.h @@ -36,6 +36,7 @@ extern void InsertColocationGroupLocally(uint32 colocationId, int shardCount, Oid distributionColumnType, Oid distributionColumnCollation); extern bool IsColocateWithNone(char *colocateWithTableName); +extern bool IsColocateWithDefault(char *colocateWithTableName); extern uint32 GetNextColocationId(void); extern void ErrorIfShardPlacementsNotColocated(Oid leftRelationId, Oid rightRelationId); extern void CheckReplicationModel(Oid sourceRelationId, Oid targetRelationId); @@ -48,5 +49,15 @@ extern void UpdateRelationColocationGroup(Oid distributedRelationId, uint32 colo extern void DeleteColocationGroupIfNoTablesBelong(uint32 colocationId); extern List * ColocationGroupTableList(uint32 colocationId, uint32 count); extern void DeleteColocationGroupLocally(uint32 colocationId); +extern uint32 FindColocateWithColocationId(Oid relationId, char replicationModel, + Oid distributionColumnType, + Oid distributionColumnCollation, + int shardCount, bool shardCountIsStrict, + char *colocateWithTableName); +extern void EnsureTableCanBeColocatedWith(Oid relationId, char replicationModel, + Oid distributionColumnType, + Oid sourceRelationId); +extern void AcquireColocationDefaultLock(void); +extern void ReleaseColocationDefaultLock(void); #endif /* COLOCATION_UTILS_H_ */ diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index 776365666..b7030adee 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -27,6 +27,12 @@ extern bool AddAllLocalTablesToMetadata; /* controlled via GUC, should be accessed via EnableLocalReferenceForeignKeys() */ extern bool EnableLocalReferenceForeignKeys; +/* + * GUC that controls whether to allow unique/exclude constraints without + * distribution column. + */ +extern bool AllowUnsafeConstraints; + extern bool EnableUnsafeTriggers; extern int MaxMatViewSizeToAutoRecreate; @@ -252,6 +258,8 @@ extern void ErrorIfUnsupportedForeignConstraintExists(Relation relation, char distributionMethod, Var *distributionColumn, uint32 colocationId); +extern void EnsureNoFKeyFromTableType(Oid relationId, int tableTypeFlag); +extern void EnsureNoFKeyToTableType(Oid relationId, int tableTypeFlag); extern void ErrorOutForFKeyBetweenPostgresAndCitusLocalTable(Oid localTableId); extern bool ColumnReferencedByAnyForeignKey(char *columnName, Oid relationId); extern bool ColumnAppearsInForeignKey(char *columnName, Oid relationId); @@ -264,7 +272,6 @@ extern List * GetForeignConstraintFromDistributedTablesCommands(Oid relationId); extern List * GetForeignConstraintCommandsInternal(Oid relationId, int flags); extern bool AnyForeignKeyDependsOnIndex(Oid indexId); extern bool HasForeignKeyWithLocalTable(Oid relationId); -extern bool HasForeignKeyToCitusLocalTable(Oid relationId); extern bool HasForeignKeyToReferenceTable(Oid relationOid); extern List * GetForeignKeysFromLocalTables(Oid relationId); extern bool TableReferenced(Oid relationOid); diff --git a/src/include/distributed/commands/utility_hook.h b/src/include/distributed/commands/utility_hook.h index b0b55a2cd..7229f7c72 100644 --- a/src/include/distributed/commands/utility_hook.h +++ b/src/include/distributed/commands/utility_hook.h @@ -104,7 +104,4 @@ extern void ResetConstraintDropped(void); extern void ExecuteDistributedDDLJob(DDLJob *ddlJob); extern void ColumnarTableSetOptionsHook(Oid relationId, ColumnarOptions options); -/* forward declarations for sending custom commands to a distributed table */ -extern DDLJob * CreateCustomDDLTaskList(Oid relationId, TableDDLCommand *command); - #endif /* MULTI_UTILITY_H */ diff --git a/src/include/distributed/distribution_column.h b/src/include/distributed/distribution_column.h index ced1be9a7..a7ec6a593 100644 --- a/src/include/distributed/distribution_column.h +++ b/src/include/distributed/distribution_column.h @@ -23,5 +23,7 @@ extern Var * BuildDistributionKeyFromColumnName(Oid relationId, char *columnName, LOCKMODE lockMode); extern char * ColumnToColumnName(Oid relationId, Node *columnNode); +extern Oid ColumnTypeIdForRelationColumnName(Oid relationId, char *columnName); +extern void EnsureValidDistributionColumn(Oid relationId, char *columnName); #endif /* DISTRIBUTION_COLUMN_H */ diff --git a/src/include/distributed/log_utils.h b/src/include/distributed/log_utils.h index ceddfc838..a9333a8a3 100644 --- a/src/include/distributed/log_utils.h +++ b/src/include/distributed/log_utils.h @@ -19,10 +19,6 @@ extern bool EnableUnsupportedFeatureMessages; extern bool IsLoggableLevel(int logLevel); -extern char * HashLogMessage(const char *text); - -#define ApplyLogRedaction(text) \ - (log_min_messages <= ereport_loglevel ? HashLogMessage(text) : text) #undef ereport diff --git a/src/include/distributed/metadata_cache.h b/src/include/distributed/metadata_cache.h index e646bce30..0ca669d2c 100644 --- a/src/include/distributed/metadata_cache.h +++ b/src/include/distributed/metadata_cache.h @@ -143,6 +143,7 @@ extern List * AllCitusTableIds(void); extern bool IsCitusTableType(Oid relationId, CitusTableType tableType); extern bool IsCitusTableTypeCacheEntry(CitusTableCacheEntry *tableEtnry, CitusTableType tableType); +extern char * GetTableTypeName(Oid tableId); extern void SetCreateCitusTransactionLevel(int val); extern int GetCitusCreationLevel(void); @@ -152,6 +153,7 @@ extern char PgDistPartitionViaCatalog(Oid relationId); extern List * LookupDistShardTuples(Oid relationId); extern char PartitionMethodViaCatalog(Oid relationId); extern Var * PartitionColumnViaCatalog(Oid relationId); +extern uint32 ColocationIdViaCatalog(Oid relationId); extern bool IsCitusLocalTableByDistParams(char partitionMethod, char replicationModel); extern List * CitusTableList(void); extern ShardInterval * LoadShardInterval(uint64 shardId); @@ -180,7 +182,6 @@ extern void FlushDistTableCache(void); extern void InvalidateMetadataSystemCache(void); extern List * CitusTableTypeIdList(CitusTableType citusTableType); extern Datum DistNodeMetadata(void); -extern bool ClusterHasReferenceTable(void); extern bool HasUniformHashDistribution(ShardInterval **shardIntervalArray, int shardIntervalArrayLength); extern bool HasUninitializedShardInterval(ShardInterval **sortedShardIntervalArray, @@ -260,8 +261,6 @@ extern Oid CitusExtraDataContainerFuncId(void); extern Oid CitusAnyValueFunctionId(void); extern Oid CitusTextSendAsJsonbFunctionId(void); extern Oid TextOutFunctionId(void); -extern Oid PgTableVisibleFuncId(void); -extern Oid CitusTableVisibleFuncId(void); extern Oid RelationIsAKnownShardFuncId(void); extern Oid JsonbExtractPathFuncId(void); extern Oid JsonbExtractPathTextFuncId(void); diff --git a/src/include/distributed/metadata_sync.h b/src/include/distributed/metadata_sync.h index e539f5f61..eb64f14fa 100644 --- a/src/include/distributed/metadata_sync.h +++ b/src/include/distributed/metadata_sync.h @@ -69,6 +69,7 @@ extern char * MarkObjectsDistributedCreateCommand(List *addresses, extern char * DistributionCreateCommand(CitusTableCacheEntry *cacheEntry); extern char * DistributionDeleteCommand(const char *schemaName, const char *tableName); +extern char * DistributionDeleteMetadataCommand(Oid relationId); extern char * TableOwnerResetCommand(Oid distributedRelationId); extern char * NodeListInsertCommand(List *workerNodeList); extern List * ShardListInsertCommand(List *shardIntervalList); diff --git a/src/include/distributed/metadata_utility.h b/src/include/distributed/metadata_utility.h index 56e15d171..793790fe0 100644 --- a/src/include/distributed/metadata_utility.h +++ b/src/include/distributed/metadata_utility.h @@ -248,6 +248,10 @@ extern void InsertIntoPgDistPartition(Oid relationId, char distributionMethod, Var *distributionColumn, uint32 colocationId, char replicationModel, bool autoConverted); extern void UpdatePgDistPartitionAutoConverted(Oid citusTableId, bool autoConverted); +extern void UpdateDistributionColumnGlobally(Oid relationId, char distributionMethod, + Var *distributionColumn, int colocationId); +extern void UpdateDistributionColumn(Oid relationId, char distributionMethod, + Var *distributionColumn, int colocationId); extern void DeletePartitionRow(Oid distributedRelationId); extern void DeleteShardRow(uint64 shardId); extern void UpdateShardPlacementState(uint64 placementId, char shardState); @@ -276,7 +280,6 @@ extern Oid TableOwnerOid(Oid relationId); extern char * TableOwner(Oid relationId); extern void EnsureTablePermissions(Oid relationId, AclMode mode); extern void EnsureTableOwner(Oid relationId); -extern void EnsureSchemaOwner(Oid schemaId); extern void EnsureHashDistributedTable(Oid relationId); extern void EnsureFunctionOwner(Oid functionId); extern void EnsureSuperUser(void); diff --git a/src/include/distributed/multi_partitioning_utils.h b/src/include/distributed/multi_partitioning_utils.h index 9f905b19d..b8cfe38c0 100644 --- a/src/include/distributed/multi_partitioning_utils.h +++ b/src/include/distributed/multi_partitioning_utils.h @@ -30,5 +30,6 @@ extern char * GeneratePartitioningInformation(Oid tableId); extern void FixPartitionConstraintsOnWorkers(Oid relationId); extern void FixLocalPartitionConstraints(Oid relationId, int64 shardId); extern void FixPartitionShardIndexNames(Oid relationId, Oid parentIndexOid); +extern List * ListShardsUnderParentRelation(Oid relationId); #endif /* MULTI_PARTITIONING_UTILS_H_ */ diff --git a/src/include/distributed/multi_physical_planner.h b/src/include/distributed/multi_physical_planner.h index 14fdd7a0c..a20085958 100644 --- a/src/include/distributed/multi_physical_planner.h +++ b/src/include/distributed/multi_physical_planner.h @@ -160,7 +160,6 @@ typedef struct Job typedef struct MapMergeJob { Job job; - Query *reduceQuery; PartitionType partitionType; Var *partitionColumn; uint32 partitionCount; @@ -551,7 +550,6 @@ extern CollateExpr * RelabelTypeToCollateExpr(RelabelType *relabelType); extern Node * BuildBaseConstraint(Var *column); extern void UpdateConstraint(Node *baseConstraint, ShardInterval *shardInterval); extern bool BinaryOpExpression(Expr *clause, Node **leftOperand, Node **rightOperand); -extern bool SimpleOpExpression(Expr *clause); /* helper functions */ extern Var * MakeInt4Column(void); diff --git a/src/include/distributed/pg_version_constants.h b/src/include/distributed/pg_version_constants.h index b3c1a0ed8..83b1071dd 100644 --- a/src/include/distributed/pg_version_constants.h +++ b/src/include/distributed/pg_version_constants.h @@ -14,5 +14,6 @@ #define PG_VERSION_13 130000 #define PG_VERSION_14 140000 #define PG_VERSION_15 150000 +#define PG_VERSION_16 160000 #endif /* PG_VERSION_CONSTANTS */ diff --git a/src/include/distributed/query_pushdown_planning.h b/src/include/distributed/query_pushdown_planning.h index 061a4a730..3c30b7814 100644 --- a/src/include/distributed/query_pushdown_planning.h +++ b/src/include/distributed/query_pushdown_planning.h @@ -46,6 +46,6 @@ extern DeferredErrorMessage * DeferErrorIfCannotPushdownSubquery(Query *subquery bool outerMostQueryHasLimit); extern DeferredErrorMessage * DeferErrorIfUnsupportedUnionQuery(Query *queryTree); - +extern bool IsJsonTableRTE(RangeTblEntry *rte); #endif /* QUERY_PUSHDOWN_PLANNING_H */ diff --git a/src/include/distributed/repair_shards.h b/src/include/distributed/repair_shards.h index fa0d76190..eb845adc2 100644 --- a/src/include/distributed/repair_shards.h +++ b/src/include/distributed/repair_shards.h @@ -17,3 +17,4 @@ extern void ErrorIfMoveUnsupportedTableType(Oid relationId); extern void CopyShardsToNode(WorkerNode *sourceNode, WorkerNode *targetNode, List *shardIntervalList, char *snapshotName); extern void VerifyTablesHaveReplicaIdentity(List *colocatedTableList); +extern bool RelationCanPublishAllModifications(Oid relationId); diff --git a/src/include/distributed/resource_lock.h b/src/include/distributed/resource_lock.h index 628d4de55..f2f6af223 100644 --- a/src/include/distributed/resource_lock.h +++ b/src/include/distributed/resource_lock.h @@ -49,8 +49,10 @@ typedef enum AdvisoryLocktagClass typedef enum CitusOperations { CITUS_TRANSACTION_RECOVERY = 0, - - CITUS_SHARD_MOVE = 1 + CITUS_NONBLOCKING_SPLIT = 1, + CITUS_CREATE_DISTRIBUTED_TABLE_CONCURRENTLY = 2, + CITUS_CREATE_COLOCATION_DEFAULT = 3, + CITUS_SHARD_MOVE = 4 } CitusOperations; /* reuse advisory lock, but with different, unused field 4 (4)*/ @@ -179,6 +181,8 @@ extern void SerializeNonCommutativeWrites(List *shardIntervalList, LOCKMODE lock extern void LockRelationShardResources(List *relationShardList, LOCKMODE lockMode); extern List * GetSortedReferenceShardIntervals(List *relationList); +void AcquireCreateDistributedTableConcurrentlyLock(Oid relationId); + /* Lock parent table's colocated shard resource */ extern void LockParentShardResourceIfPartition(List *shardIntervalList, LOCKMODE lockMode); diff --git a/src/include/distributed/shard_split.h b/src/include/distributed/shard_split.h index 7464534b7..2154ff446 100644 --- a/src/include/distributed/shard_split.h +++ b/src/include/distributed/shard_split.h @@ -12,6 +12,8 @@ #ifndef SHARDSPLIT_H_ #define SHARDSPLIT_H_ +#include "distributed/utils/distribution_column_map.h" + /* Split Modes supported by Shard Split API */ typedef enum SplitMode { @@ -28,10 +30,10 @@ typedef enum SplitMode typedef enum SplitOperation { SHARD_SPLIT_API = 0, - ISOLATE_TENANT_TO_NEW_SHARD + ISOLATE_TENANT_TO_NEW_SHARD, + CREATE_DISTRIBUTED_TABLE } SplitOperation; - /* * SplitShard API to split a given shard (or shard group) using split mode and * specified split points to a set of destination nodes. @@ -40,10 +42,15 @@ extern void SplitShard(SplitMode splitMode, SplitOperation splitOperation, uint64 shardIdToSplit, List *shardSplitPointsList, - List *nodeIdsForPlacementList); + List *nodeIdsForPlacementList, + DistributionColumnMap *distributionColumnOverrides, + List *colocatedShardIntervalList, + uint32 targetColocationId); extern void DropShardList(List *shardIntervalList); extern SplitMode LookupSplitMode(Oid shardTransferModeOid); +extern void ErrorIfMultipleNonblockingMoveSplitInTheSameTransaction(void); + #endif /* SHARDSPLIT_H_ */ diff --git a/src/include/distributed/shardsplit_logical_replication.h b/src/include/distributed/shardsplit_logical_replication.h index b47861369..a7dc3485e 100644 --- a/src/include/distributed/shardsplit_logical_replication.h +++ b/src/include/distributed/shardsplit_logical_replication.h @@ -47,4 +47,5 @@ extern void DropShardSplitPublications(MultiConnection *sourceConnection, extern void DropShardSplitSubsriptions(List *shardSplitSubscribersMetadataList); extern void DropShardSplitReplicationSlots(MultiConnection *sourceConnection, List *replicationSlotInfoList); + #endif /* SHARDSPLIT_LOGICAL_REPLICATION_H */ diff --git a/src/include/distributed/utils/directory.h b/src/include/distributed/utils/directory.h index 48294cd00..7ed8a3f95 100644 --- a/src/include/distributed/utils/directory.h +++ b/src/include/distributed/utils/directory.h @@ -18,7 +18,6 @@ #define PG_JOB_CACHE_DIR "pgsql_job_cache" -extern bool CacheDirectoryElement(const char *filename); extern void CleanupJobCacheDirectory(void); extern void CitusCreateDirectory(StringInfo directoryName); extern void CitusRemoveDirectory(const char *filename); diff --git a/src/include/distributed/utils/distribution_column_map.h b/src/include/distributed/utils/distribution_column_map.h new file mode 100644 index 000000000..1b671c88b --- /dev/null +++ b/src/include/distributed/utils/distribution_column_map.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * distribution_column_map.h + * Declarations for a relation OID to distribution column hash. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#ifndef DISTRIBUTION_COLUMN_HASH_H +#define DISTRIBUTION_COLUMN_HASH_H + +#include "postgres.h" + +#include "nodes/primnodes.h" +#include "utils/hsearch.h" + + +typedef HTAB DistributionColumnMap; + + +extern DistributionColumnMap * CreateDistributionColumnMap(void); +extern void AddDistributionColumnForRelation(DistributionColumnMap *distributionColumns, + Oid relationId, + char *distributionColumnName); +extern Var * GetDistributionColumnFromMap(DistributionColumnMap *distributionColumnMap, + Oid relationId); +extern Var * GetDistributionColumnWithOverrides(Oid relationId, + DistributionColumnMap *overrides); + +#endif /* DISTRIBUTION_COLUMN_HASH_H */ diff --git a/src/include/distributed/worker_manager.h b/src/include/distributed/worker_manager.h index 1c5b27f1f..4cc3e5c6f 100644 --- a/src/include/distributed/worker_manager.h +++ b/src/include/distributed/worker_manager.h @@ -90,6 +90,7 @@ extern WorkerNode * FindWorkerNodeAnyCluster(const char *nodeName, int32 nodePor extern WorkerNode * FindNodeWithNodeId(int nodeId, bool missingOk); extern List * ReadDistNode(bool includeNodesFromOtherClusters); extern void EnsureCoordinator(void); +extern void EnsureCoordinatorIsInMetadata(void); extern void InsertCoordinatorIfClusterEmpty(void); extern uint32 GroupForNode(char *nodeName, int32 nodePort); extern WorkerNode * PrimaryNodeForGroup(int32 groupId, bool *groupContainsNodes); diff --git a/src/include/distributed/worker_transaction.h b/src/include/distributed/worker_transaction.h index 72b16acd5..689a9e192 100644 --- a/src/include/distributed/worker_transaction.h +++ b/src/include/distributed/worker_transaction.h @@ -79,11 +79,6 @@ extern void SendMetadataCommandListToWorkerListInCoordinatedTransaction( const char * nodeUser, List *commandList); -extern void SendCommandToWorkersOptionalInParallel(TargetWorkerSet targetWorkerSet, - const char *command, - const char *user); -void SendCommandToWorkersInParallel(TargetWorkerSet targetWorkerSet, - const char *command, const char *user); extern void RemoveWorkerTransaction(const char *nodeName, int32 nodePort); /* helper functions for worker transactions */ diff --git a/src/include/pg_version_compat.h b/src/include/pg_version_compat.h index 2e242cfe1..f551085a7 100644 --- a/src/include/pg_version_compat.h +++ b/src/include/pg_version_compat.h @@ -39,6 +39,8 @@ typedef Value String; #define pgstat_init_relation(r) pgstat_initstats(r) #define pg_analyze_and_rewrite_fixedparams(a, b, c, d, e) pg_analyze_and_rewrite(a, b, c, \ d, e) +#define boolVal(v) intVal(v) +#define makeBoolean(val) makeInteger(val) static inline int64 pg_strtoint64(char *s) diff --git a/src/test/columnar_freezing/Makefile b/src/test/columnar_freezing/Makefile index 565e204fd..cd364cdbc 100644 --- a/src/test/columnar_freezing/Makefile +++ b/src/test/columnar_freezing/Makefile @@ -10,6 +10,25 @@ subdir = src/test/columnar_freezing top_builddir = ../../.. include $(top_builddir)/Makefile.global +# In PG15, Perl test modules have been moved to a new namespace +# new() and get_new_node() methods have been unified to 1 method: new() +# Relevant PG commits 201a76183e2056c2217129e12d68c25ec9c559c8 +# b3b4d8e68ae83f432f43f035c7eb481ef93e1583 +pg_version = $(shell $(PG_CONFIG) --version 2>/dev/null) +pg_whole_version = $(shell echo "$(pg_version)"| sed -e 's/^PostgreSQL \([0-9]*\)\(\.[0-9]*\)\{0,1\}\(.*\)/\1\2/') +pg_major_version = $(shell echo "$(pg_whole_version)"| sed -e 's/^\([0-9]\{2\}\)\(.*\)/\1/') + +# for now, we only have a single test file +# due to the above explanation, we ended up separating the test paths for +# different versions. If you need to add new test files, be careful to add both versions +ifeq ($(pg_major_version),13) + test_path = t_pg13_pg14/*.pl +else ifeq ($(pg_major_version),14) + test_path = t_pg13_pg14/*.pl +else + test_path = t/*.pl +endif + # copied from pgxs/Makefile.global to use postgres' abs build dir for pg_regress ifeq ($(enable_tap_tests),yes) @@ -23,7 +42,7 @@ PGPORT='6$(DEF_PGPORT)' \ top_builddir='$(CURDIR)/$(top_builddir)' \ PG_REGRESS='$(pgxsdir)/src/test/regress/pg_regress' \ TEMP_CONFIG='$(CURDIR)'/postgresql.conf \ -$(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl) +$(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),$(test_path)) endef else diff --git a/src/test/columnar_freezing/t/001_columnar_freezing.pl b/src/test/columnar_freezing/t/001_columnar_freezing.pl index 1985da2a5..01e8346cf 100644 --- a/src/test/columnar_freezing/t/001_columnar_freezing.pl +++ b/src/test/columnar_freezing/t/001_columnar_freezing.pl @@ -1,12 +1,12 @@ # Minimal test testing streaming replication use strict; use warnings; -use PostgresNode; -use TestLib; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; use Test::More tests => 2; # Initialize single node -my $node_one = get_new_node('node_one'); +my $node_one = PostgreSQL::Test::Cluster->new('node_one'); $node_one->init(); $node_one->start; diff --git a/src/test/columnar_freezing/t_pg13_pg14/001_columnar_freezing_pg13_pg14.pl b/src/test/columnar_freezing/t_pg13_pg14/001_columnar_freezing_pg13_pg14.pl new file mode 100644 index 000000000..1985da2a5 --- /dev/null +++ b/src/test/columnar_freezing/t_pg13_pg14/001_columnar_freezing_pg13_pg14.pl @@ -0,0 +1,52 @@ +# Minimal test testing streaming replication +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 2; + +# Initialize single node +my $node_one = get_new_node('node_one'); +$node_one->init(); +$node_one->start; + +# initialize the citus extension +$node_one->safe_psql('postgres', "CREATE EXTENSION citus;"); + +# create columnar table and insert simple data to verify the data survives a crash +$node_one->safe_psql('postgres', " +CREATE TABLE test_row(i int); +INSERT INTO test_row VALUES (1); +CREATE TABLE test_columnar_freeze(i int) USING columnar WITH(autovacuum_enabled=false); +INSERT INTO test_columnar_freeze VALUES (1); +"); + +my $ten_thousand_updates = ""; + +foreach (1..10000) { + $ten_thousand_updates .= "UPDATE test_row SET i = i + 1;\n"; +} + +# 70K updates +foreach (1..7) { + $node_one->safe_psql('postgres', $ten_thousand_updates); +} + +my $result = $node_one->safe_psql('postgres', " +select age(relfrozenxid) < 70000 as was_frozen + from pg_class where relname='test_columnar_freeze'; +"); +print "node one count: $result\n"; +is($result, qq(f), 'columnar table was not frozen'); + +$node_one->safe_psql('postgres', 'VACUUM FREEZE test_columnar_freeze;'); + +$result = $node_one->safe_psql('postgres', " +select age(relfrozenxid) < 70000 as was_frozen + from pg_class where relname='test_columnar_freeze'; +"); +print "node one count: $result\n"; +is($result, qq(t), 'columnar table was frozen'); + +$node_one->stop('fast'); + diff --git a/src/test/recovery/Makefile b/src/test/recovery/Makefile index cfe3e71b9..03e18fa0c 100644 --- a/src/test/recovery/Makefile +++ b/src/test/recovery/Makefile @@ -12,6 +12,25 @@ subdir = src/test/recovery top_builddir = ../../.. include $(top_builddir)/Makefile.global +# In PG15, Perl test modules have been moved to a new namespace +# new() and get_new_node() methods have been unified to 1 method: new() +# Relevant PG commits 201a76183e2056c2217129e12d68c25ec9c559c8 +# b3b4d8e68ae83f432f43f035c7eb481ef93e1583 +pg_version = $(shell $(PG_CONFIG) --version 2>/dev/null) +pg_whole_version = $(shell echo "$(pg_version)"| sed -e 's/^PostgreSQL \([0-9]*\)\(\.[0-9]*\)\{0,1\}\(.*\)/\1\2/') +pg_major_version = $(shell echo "$(pg_whole_version)"| sed -e 's/^\([0-9]\{2\}\)\(.*\)/\1/') + +# for now, we only have a single test file +# due to the above explanation, we ended up separating the test paths for +# different versions. If you need to add new test files, be careful to add both versions +ifeq ($(pg_major_version),13) + test_path = t_pg13_pg14/*.pl +else ifeq ($(pg_major_version),14) + test_path = t_pg13_pg14/*.pl +else + test_path = t/*.pl +endif + # copied from pgxs/Makefile.global to use postgres' abs build dir for pg_regress ifeq ($(enable_tap_tests),yes) @@ -25,7 +44,7 @@ PGPORT='6$(DEF_PGPORT)' \ top_builddir='$(CURDIR)/$(top_builddir)' \ PG_REGRESS='$(pgxsdir)/src/test/regress/pg_regress' \ TEMP_CONFIG='$(CURDIR)'/postgresql.conf \ -$(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl) +$(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),$(test_path)) endef else diff --git a/src/test/recovery/t/001_columnar_crash_recovery.pl b/src/test/recovery/t/001_columnar_crash_recovery.pl index 9ea87835f..7dee21dd1 100644 --- a/src/test/recovery/t/001_columnar_crash_recovery.pl +++ b/src/test/recovery/t/001_columnar_crash_recovery.pl @@ -1,12 +1,12 @@ # Minimal test testing streaming replication use strict; use warnings; -use PostgresNode; -use TestLib; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; use Test::More tests => 6; # Initialize single node -my $node_one = get_new_node('node_one'); +my $node_one = PostgreSQL::Test::Cluster->new('node_one'); $node_one->init(); $node_one->start; diff --git a/src/test/recovery/t_pg13_pg14/001_columnar_crash_recovery_pg13_pg14.pl b/src/test/recovery/t_pg13_pg14/001_columnar_crash_recovery_pg13_pg14.pl new file mode 100644 index 000000000..9ea87835f --- /dev/null +++ b/src/test/recovery/t_pg13_pg14/001_columnar_crash_recovery_pg13_pg14.pl @@ -0,0 +1,98 @@ +# Minimal test testing streaming replication +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 6; + +# Initialize single node +my $node_one = get_new_node('node_one'); +$node_one->init(); +$node_one->start; + +# initialize the citus extension +$node_one->safe_psql('postgres', "CREATE EXTENSION citus;"); + +# create columnar table and insert simple data to verify the data survives a crash +$node_one->safe_psql('postgres', " +BEGIN; +CREATE TABLE t1 (a int, b text) USING columnar; +INSERT INTO t1 SELECT a, 'hello world ' || a FROM generate_series(1,1002) AS a; +COMMIT; +"); + +# simulate crash +$node_one->stop('immediate'); +$node_one->start; + +my $result = $node_one->safe_psql('postgres', "SELECT count(*) FROM t1;"); +print "node one count: $result\n"; +is($result, qq(1002), 'columnar recovered data from before crash'); + + +# truncate the table to verify the truncation survives a crash +$node_one->safe_psql('postgres', " +TRUNCATE t1; +"); + +# simulate crash +$node_one->stop('immediate'); +$node_one->start; + +$result = $node_one->safe_psql('postgres', "SELECT count(*) FROM t1;"); +print "node one count: $result\n"; +is($result, qq(0), 'columnar recovered truncation'); + +# test crashing while having an open transaction +$node_one->safe_psql('postgres', " +BEGIN; +INSERT INTO t1 SELECT a, 'hello world ' || a FROM generate_series(1,1003) AS a; +"); + +# simulate crash +$node_one->stop('immediate'); +$node_one->start; + +$result = $node_one->safe_psql('postgres', "SELECT count(*) FROM t1;"); +print "node one count: $result\n"; +is($result, qq(0), 'columnar crash during uncommitted transaction'); + +# test crashing while having a prepared transaction +$node_one->safe_psql('postgres', " +BEGIN; +INSERT INTO t1 SELECT a, 'hello world ' || a FROM generate_series(1,1004) AS a; +PREPARE TRANSACTION 'prepared_xact_crash'; +"); + +# simulate crash +$node_one->stop('immediate'); +$node_one->start; + +$result = $node_one->safe_psql('postgres', "SELECT count(*) FROM t1;"); +print "node one count: $result\n"; +is($result, qq(0), 'columnar crash during prepared transaction (before commit)'); + +$node_one->safe_psql('postgres', " +COMMIT PREPARED 'prepared_xact_crash'; +"); + +$result = $node_one->safe_psql('postgres', "SELECT count(*) FROM t1;"); +print "node one count: $result\n"; +is($result, qq(1004), 'columnar crash during prepared transaction (after commit)'); + +# test crash recovery with copied data +$node_one->safe_psql('postgres', " +\\copy t1 FROM stdin delimiter ',' +1,a +2,b +3,c +\\. +"); + +# simulate crash +$node_one->stop('immediate'); +$node_one->start; + +$result = $node_one->safe_psql('postgres', "SELECT count(*) FROM t1;"); +print "node one count: $result\n"; +is($result, qq(1007), 'columnar crash after copy command'); diff --git a/src/test/regress/Makefile b/src/test/regress/Makefile index f71825756..7c42ed556 100644 --- a/src/test/regress/Makefile +++ b/src/test/regress/Makefile @@ -16,9 +16,13 @@ MAKEFILE_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) export PATH := $(MAKEFILE_DIR)/bin:$(PATH) export PG_REGRESS_DIFF_OPTS = -dU10 -w # Use lower isolation test timeout, the 5 minute default is waaay too long for -# us so we use 20 seconds instead. We should detect blockages very quickly and -# the queries we run are also very fast. -export PGISOLATIONTIMEOUT = 20 +# us so we use 60 seconds instead. We should detect blockages very quickly and +# most queries that we run are also very fast. So fast even that 60 seconds is +# usually too long. However, any commands involving logical replication can be +# quite slow, especially shard splits and especially on CI. So we still keep +# this value at the pretty high 60 seconds because even those slow commands are +# definitly stuck when they take longer than that. +export PGISOLATIONTIMEOUT = 60 ## ## Citus regression support diff --git a/src/test/regress/base_schedule b/src/test/regress/base_schedule index 0a2ada96f..65f439acc 100644 --- a/src/test/regress/base_schedule +++ b/src/test/regress/base_schedule @@ -4,7 +4,8 @@ test: multi_test_helpers multi_test_helpers_superuser multi_create_fdw columnar_test_helpers failure_test_helpers test: multi_cluster_management test: multi_test_catalog_views -test: multi_create_table multi_behavioral_analytics_create_table +test: multi_create_table +test: multi_behavioral_analytics_create_table test: multi_create_table_superuser multi_behavioral_analytics_create_table_superuser test: multi_load_data multi_load_data_superuser tablespace test: check_mx diff --git a/src/test/regress/bin/normalize.sed b/src/test/regress/bin/normalize.sed index 19bbdaaea..97d9d5da4 100644 --- a/src/test/regress/bin/normalize.sed +++ b/src/test/regress/bin/normalize.sed @@ -116,6 +116,17 @@ s/(ERROR: |WARNING: |error:) server closed the connection unexpectedly/\1 connec /^\s*connection not open$/d #endif /* (PG_VERSION_NUM >= PG_VERSION_13) && (PG_VERSION_NUM < PG_VERSION_14) */ +# Changed outputs after minor bump to PG14.5 and PG13.8 +s/(ERROR: |WARNING: |error:) invalid socket/\1 connection not open/g + +# Extra outputs after minor bump to PG14.5 and PG13.8 +/^\s*invalid socket$/d + +# pg15 changes +s/is not a PostgreSQL server process/is not a PostgreSQL backend process/g +s/ AS "\?column\?"//g +s/".*\.(.*)": (found .* removable)/"\1": \2/g + # intermediate_results s/(ERROR.*)pgsql_job_cache\/([0-9]+_[0-9]+_[0-9]+)\/(.*).data/\1pgsql_job_cache\/xx_x_xxx\/\3.data/g diff --git a/src/test/regress/citus_tests/common.py b/src/test/regress/citus_tests/common.py index dcde8a5af..a6169d8c5 100644 --- a/src/test/regress/citus_tests/common.py +++ b/src/test/regress/citus_tests/common.py @@ -50,7 +50,9 @@ def initialize_db_for_cluster(pg_path, rel_data_path, settings, node_names): # private keys correctly "--allow-group-access", "--encoding", - "UTF8" + "UTF8", + "--locale", + "POSIX" ] subprocess.run(command, check=True) add_settings(abs_data_path, settings) @@ -135,8 +137,10 @@ def create_citus_extension(pg_path, node_ports): def run_pg_regress(pg_path, pg_srcdir, port, schedule): should_exit = True - _run_pg_regress(pg_path, pg_srcdir, port, schedule, should_exit) - subprocess.run("bin/copy_modified", check=True) + try: + _run_pg_regress(pg_path, pg_srcdir, port, schedule, should_exit) + finally: + subprocess.run("bin/copy_modified", check=True) def run_pg_regress_without_exit( diff --git a/src/test/regress/citus_tests/config.py b/src/test/regress/citus_tests/config.py index 4ebe96143..207279f92 100644 --- a/src/test/regress/citus_tests/config.py +++ b/src/test/regress/citus_tests/config.py @@ -105,7 +105,7 @@ class CitusBaseClusterConfig(object, metaclass=NewInitCaller): "citus.enable_repartition_joins": True, "citus.repartition_join_bucket_count_per_node": 2, "citus.log_distributed_deadlock_detection": True, - "max_connections": 600, + "max_connections": 1200, } self.new_settings = {} self.add_coordinator_to_metadata = False diff --git a/src/test/regress/enterprise_isolation_logicalrep_3_schedule b/src/test/regress/enterprise_isolation_logicalrep_3_schedule index 1d38764bb..105dcc049 100644 --- a/src/test/regress/enterprise_isolation_logicalrep_3_schedule +++ b/src/test/regress/enterprise_isolation_logicalrep_3_schedule @@ -6,3 +6,4 @@ test: isolation_setup test: isolation_cluster_management test: isolation_logical_replication_with_partitioning +test: isolation_logical_replication_binaryless diff --git a/src/test/regress/expected/adaptive_executor.out b/src/test/regress/expected/adaptive_executor.out index 73ba772b5..aeaa553f2 100644 --- a/src/test/regress/expected/adaptive_executor.out +++ b/src/test/regress/expected/adaptive_executor.out @@ -10,8 +10,35 @@ SELECT create_distributed_table('test','x'); (1 row) +-- Add 1 row to each shard +SELECT get_shard_id_for_distribution_column('test', 1); + get_shard_id_for_distribution_column +--------------------------------------------------------------------- + 801009000 +(1 row) + INSERT INTO test VALUES (1,2); +SELECT get_shard_id_for_distribution_column('test', 3); + get_shard_id_for_distribution_column +--------------------------------------------------------------------- + 801009001 +(1 row) + INSERT INTO test VALUES (3,2); +SELECT get_shard_id_for_distribution_column('test', 6); + get_shard_id_for_distribution_column +--------------------------------------------------------------------- + 801009002 +(1 row) + +INSERT INTO test VALUES (8,2); +SELECT get_shard_id_for_distribution_column('test', 11); + get_shard_id_for_distribution_column +--------------------------------------------------------------------- + 801009003 +(1 row) + +INSERT INTO test VALUES (11,2); -- Set a very high slow start to avoid opening parallel connections SET citus.executor_slow_start_interval TO '60s'; SET citus.max_adaptive_executor_pool_size TO 2; @@ -19,7 +46,7 @@ BEGIN; SELECT count(*) FROM test a JOIN (SELECT x, pg_sleep(0.1) FROM test) b USING (x); count --------------------------------------------------------------------- - 2 + 4 (1 row) SELECT sum(result::bigint) FROM run_command_on_workers($$ @@ -35,10 +62,10 @@ END; -- SELECT takes longer than slow start interval, should open multiple connections SET citus.executor_slow_start_interval TO '10ms'; BEGIN; -SELECT count(*) FROM test a JOIN (SELECT x, pg_sleep(0.1) FROM test) b USING (x); +SELECT count(*) FROM test a JOIN (SELECT x, pg_sleep(0.2) FROM test) b USING (x); count --------------------------------------------------------------------- - 2 + 4 (1 row) SELECT sum(result::bigint) FROM run_command_on_workers($$ diff --git a/src/test/regress/expected/alter_distributed_table.out b/src/test/regress/expected/alter_distributed_table.out index b29b10168..02c5b2ca6 100644 --- a/src/test/regress/expected/alter_distributed_table.out +++ b/src/test/regress/expected/alter_distributed_table.out @@ -321,6 +321,55 @@ SELECT * FROM partitioned_table_6_10 ORDER BY 1, 2; 7 | 2 (1 row) +-- test altering partitioned table colocate_with:none +CREATE TABLE foo (x int, y int, t timestamptz default now()) PARTITION BY RANGE (t); +CREATE TABLE foo_1 PARTITION of foo for VALUES FROM ('2022-01-01') TO ('2022-12-31'); +CREATE TABLE foo_2 PARTITION of foo for VALUES FROM ('2023-01-01') TO ('2023-12-31'); +SELECT create_distributed_table('foo','x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE foo_bar (x int, y int, t timestamptz default now()) PARTITION BY RANGE (t); +CREATE TABLE foo_bar_1 PARTITION of foo_bar for VALUES FROM ('2022-01-01') TO ('2022-12-31'); +CREATE TABLE foo_bar_2 PARTITION of foo_bar for VALUES FROM ('2023-01-01') TO ('2023-12-31'); +SELECT create_distributed_table('foo_bar','x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT COUNT(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid::regclass::text in ('foo', 'foo_1', 'foo_2', 'foo_bar', 'foo_bar_1', 'foo_bar_2'); + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT alter_distributed_table('foo', colocate_with => 'none'); +NOTICE: converting the partitions of alter_distributed_table.foo +NOTICE: creating a new table for alter_distributed_table.foo_1 +NOTICE: moving the data of alter_distributed_table.foo_1 +NOTICE: dropping the old alter_distributed_table.foo_1 +NOTICE: renaming the new table to alter_distributed_table.foo_1 +NOTICE: creating a new table for alter_distributed_table.foo_2 +NOTICE: moving the data of alter_distributed_table.foo_2 +NOTICE: dropping the old alter_distributed_table.foo_2 +NOTICE: renaming the new table to alter_distributed_table.foo_2 +NOTICE: creating a new table for alter_distributed_table.foo +NOTICE: dropping the old alter_distributed_table.foo +NOTICE: renaming the new table to alter_distributed_table.foo + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT COUNT(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid::regclass::text in ('foo', 'foo_1', 'foo_2', 'foo_bar', 'foo_bar_1', 'foo_bar_2'); + count +--------------------------------------------------------------------- + 2 +(1 row) + -- test references CREATE TABLE referenced_dist_table (a INT UNIQUE); CREATE TABLE referenced_ref_table (a INT UNIQUE); diff --git a/src/test/regress/expected/binary_protocol.out b/src/test/regress/expected/binary_protocol.out index 85fbbdecf..4b9a0eb18 100644 --- a/src/test/regress/expected/binary_protocol.out +++ b/src/test/regress/expected/binary_protocol.out @@ -1,4 +1,5 @@ SET citus.shard_count = 2; +SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 4754000; CREATE SCHEMA binary_protocol; SET search_path TO binary_protocol, public; @@ -196,6 +197,13 @@ SELECT ARRAY[(col, col)::nested_composite_type_domain] FROM composite_type_table {"(\"(1,2)\",\"(1,2)\")"} (1 row) +-- Confirm that aclitem doesn't have receive and send functions +SELECT typreceive, typsend FROM pg_type WHERE typname = 'aclitem'; + typreceive | typsend +--------------------------------------------------------------------- + - | - +(1 row) + CREATE TABLE binaryless_builtin ( col1 aclitem NOT NULL, col2 character varying(255) NOT NULL diff --git a/src/test/regress/expected/citus_local_tables_queries.out b/src/test/regress/expected/citus_local_tables_queries.out index 4399062aa..8f6c47393 100644 --- a/src/test/regress/expected/citus_local_tables_queries.out +++ b/src/test/regress/expected/citus_local_tables_queries.out @@ -1,3 +1,17 @@ +-- +-- CITUS_LOCAL_TABLES_QUERIES +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + \set VERBOSITY terse SET citus.next_shard_id TO 1509000; SET citus.shard_replication_factor TO 1; @@ -570,7 +584,7 @@ SELECT clear_and_init_test_tables(); INSERT INTO citus_local_table SELECT * from reference_table; -NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) SELECT a, b FROM citus_local_table_queries.reference_table_1509003 reference_table +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) SELECT reference_table.a, reference_table.b FROM citus_local_table_queries.reference_table_1509003 reference_table INSERT INTO reference_table SELECT * from citus_local_table; NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table @@ -583,7 +597,7 @@ SELECT * from citus_local_table; NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table INSERT INTO citus_local_table SELECT * from citus_local_table_2; -NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) SELECT a, b FROM citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2 +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) SELECT citus_local_table_2.a, citus_local_table_2.b FROM citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2 INSERT INTO citus_local_table SELECT * from citus_local_table_2 ORDER BY 1,2 diff --git a/src/test/regress/expected/citus_local_tables_queries_0.out b/src/test/regress/expected/citus_local_tables_queries_0.out new file mode 100644 index 000000000..4b8d3411e --- /dev/null +++ b/src/test/regress/expected/citus_local_tables_queries_0.out @@ -0,0 +1,1165 @@ +-- +-- CITUS_LOCAL_TABLES_QUERIES +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +\set VERBOSITY terse +SET citus.next_shard_id TO 1509000; +SET citus.shard_replication_factor TO 1; +SET citus.enable_local_execution TO ON; +SET citus.log_local_commands TO ON; +CREATE SCHEMA citus_local_table_queries; +SET search_path TO citus_local_table_queries; +-- ensure that coordinator is added to pg_dist_node +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 dummy_reference_table(a int unique, b int); +SELECT create_reference_table('dummy_reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE citus_local_table(a int, b int); +ALTER TABLE citus_local_table ADD CONSTRAINT fkey_to_dummy_1 FOREIGN KEY (a) REFERENCES dummy_reference_table(a); +NOTICE: executing the command locally: SELECT worker_apply_inter_shard_ddl_command (1509001, 'citus_local_table_queries', 1509000, 'citus_local_table_queries', 'ALTER TABLE citus_local_table ADD CONSTRAINT fkey_to_dummy_1 FOREIGN KEY (a) REFERENCES dummy_reference_table(a);') +CREATE TABLE citus_local_table_2(a int, b int); +ALTER TABLE citus_local_table_2 ADD CONSTRAINT fkey_to_dummy_2 FOREIGN KEY (a) REFERENCES dummy_reference_table(a); +NOTICE: executing the command locally: SELECT worker_apply_inter_shard_ddl_command (1509002, 'citus_local_table_queries', 1509000, 'citus_local_table_queries', 'ALTER TABLE citus_local_table_2 ADD CONSTRAINT fkey_to_dummy_2 FOREIGN KEY (a) REFERENCES dummy_reference_table(a);') +CREATE TABLE reference_table(a int, b int); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE distributed_table(a int, b int); +SELECT create_distributed_table('distributed_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE postgres_local_table(a int, b int); +-- Define a helper function to truncate & insert some data into our test tables +-- We should call this function at some places in this test file to prevent +-- test to take a long time. +-- We shouldn't use LIMIT in INSERT SELECT queries to make the test faster as +-- LIMIT would force planner to wrap SELECT query in an intermediate result and +-- this might reduce the coverage of the test cases. +CREATE FUNCTION clear_and_init_test_tables() RETURNS void AS $$ + BEGIN + SET client_min_messages to ERROR; + + TRUNCATE postgres_local_table, citus_local_table, reference_table, distributed_table, dummy_reference_table, citus_local_table_2; + + INSERT INTO dummy_reference_table SELECT i, i FROM generate_series(0, 5) i; + INSERT INTO citus_local_table SELECT i, i FROM generate_series(0, 5) i; + INSERT INTO citus_local_table_2 SELECT i, i FROM generate_series(0, 5) i; + INSERT INTO postgres_local_table SELECT i, i FROM generate_series(0, 5) i; + INSERT INTO distributed_table SELECT i, i FROM generate_series(0, 5) i; + INSERT INTO reference_table SELECT i, i FROM generate_series(0, 5) i; + + RESET client_min_messages; + END; +$$ LANGUAGE plpgsql; +--------------------------------------------------------------------- +---- SELECT ---- +--------------------------------------------------------------------- +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +-- join between citus local tables and reference tables would succeed +SELECT count(*) FROM citus_local_table, reference_table WHERE citus_local_table.a = reference_table.a; +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table, citus_local_table_queries.reference_table_1509003 reference_table WHERE (citus_local_table.a OPERATOR(pg_catalog.=) reference_table.a) + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT * FROM citus_local_table, reference_table WHERE citus_local_table.a = reference_table.a ORDER BY 1,2,3,4 FOR UPDATE; +NOTICE: executing the command locally: SELECT citus_local_table.a, citus_local_table.b, reference_table.a, reference_table.b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table, citus_local_table_queries.reference_table_1509003 reference_table WHERE (citus_local_table.a OPERATOR(pg_catalog.=) reference_table.a) ORDER BY citus_local_table.a, citus_local_table.b, reference_table.a, reference_table.b FOR UPDATE OF citus_local_table FOR UPDATE OF reference_table + a | b | a | b +--------------------------------------------------------------------- + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 + 2 | 2 | 2 | 2 + 3 | 3 | 3 | 3 + 4 | 4 | 4 | 4 + 5 | 5 | 5 | 5 +(6 rows) + +-- should work +WITH cte_1 AS + (SELECT * FROM citus_local_table, reference_table WHERE citus_local_table.a = reference_table.a ORDER BY 1,2,3,4 FOR UPDATE) +SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: WITH cte_1 AS (SELECT citus_local_table.a, citus_local_table.b, reference_table.a, reference_table.b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table, citus_local_table_queries.reference_table_1509003 reference_table WHERE (citus_local_table.a OPERATOR(pg_catalog.=) reference_table.a) ORDER BY citus_local_table.a, citus_local_table.b, reference_table.a, reference_table.b FOR UPDATE OF citus_local_table FOR UPDATE OF reference_table) SELECT count(*) AS count FROM cte_1 cte_1(a, b, a_1, b_1) + count +--------------------------------------------------------------------- + 6 +(1 row) + +-- should work as joins are between ctes +WITH cte_citus_local_table AS + (SELECT * FROM citus_local_table), +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_citus_local_table, cte_postgres_local_table +WHERE cte_citus_local_table.a = 1 AND cte_distributed_table.a = 1; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +-- should fail as we don't support direct joins between distributed/local tables +SELECT count(*) FROM distributed_table d1, distributed_table d2, citus_local_table; +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +-- local table inside subquery should just work +SELECT count(*) FROM +( + SELECT * FROM (SELECT * FROM citus_local_table) as subquery_inner +) as subquery_top; +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT subquery_inner.a, subquery_inner.b FROM (SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table) subquery_inner) subquery_top + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +-- join between citus/postgres local tables should just work +SELECT count(*) FROM +( + SELECT * FROM (SELECT count(*) FROM citus_local_table, postgres_local_table) as subquery_inner +) as subquery_top; +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT subquery_inner.count FROM (SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table, citus_local_table_queries.postgres_local_table) subquery_inner) subquery_top + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- should fail as we don't support direct joins between distributed/local tables +SELECT count(*) FROM +( + SELECT *, random() FROM (SELECT *, random() FROM citus_local_table, distributed_table) as subquery_inner +) as subquery_top; +NOTICE: executing the command locally: SELECT NULL::integer AS "dummy-1" FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true + count +--------------------------------------------------------------------- + 36 +(1 row) + +-- should fail as we don't support direct joins between distributed/local tables +SELECT count(*) FROM +( + SELECT *, random() + FROM ( + WITH cte_1 AS (SELECT *, random() FROM citus_local_table, distributed_table) SELECT * FROM cte_1) as subquery_inner +) as subquery_top; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT subquery_inner.a, subquery_inner.b, subquery_inner.a_1 AS a, subquery_inner.b_1 AS b, subquery_inner.random, random() AS random FROM (SELECT cte_1.a, cte_1.b, cte_1.a_1 AS a, cte_1.b_1 AS b, cte_1.random FROM (SELECT intermediate_result.a, intermediate_result.b, intermediate_result.a_1 AS a, intermediate_result.b_1 AS b, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer, a_1 integer, b_1 integer, random double precision)) cte_1(a, b, a_1, b_1, random)) subquery_inner(a, b, a_1, b_1, random)) subquery_top(a, b, a_1, b_1, random, random_1) + count +--------------------------------------------------------------------- + 36 +(1 row) + +-- should be fine +SELECT count(*) FROM +( + SELECT *, random() + FROM ( + WITH cte_1 AS (SELECT *, random() FROM citus_local_table), cte_2 AS (SELECT * FROM distributed_table) SELECT count(*) FROM cte_1, cte_2 + ) as subquery_inner +) as subquery_top; +NOTICE: executing the command locally: SELECT a, b, random() AS random FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT subquery_inner.count, random() AS random FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) subquery_inner) subquery_top + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +-- prepared statement +PREPARE citus_local_only AS SELECT count(*) FROM citus_local_table; +-- execute 6 times, local tables without params +EXECUTE citus_local_only; +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +EXECUTE citus_local_only; +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +EXECUTE citus_local_only; +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +EXECUTE citus_local_only; +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +EXECUTE citus_local_only; +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +EXECUTE citus_local_only; +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +-- execute 6 times, with param +PREPARE citus_local_only_p(int) AS SELECT count(*) FROM citus_local_table WHERE a = $1; +EXECUTE citus_local_only_p(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE citus_local_only_p(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE citus_local_only_p(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE citus_local_only_p(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE citus_local_only_p(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE citus_local_only_p(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- do not evalute the function +-- show the logs +EXECUTE citus_local_only_p(random()); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE citus_local_only_p(random()); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) $1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +PREPARE mixed_query(int, int, int) AS + WITH cte_citus_local_table AS + (SELECT * FROM citus_local_table WHERE a = $1), + cte_postgres_local_table AS + (SELECT * FROM postgres_local_table WHERE a = $2), + cte_distributed_table AS + (SELECT * FROM distributed_table WHERE a = $3), + cte_mixes AS (SELECT * FROM cte_distributed_table, cte_citus_local_table, cte_postgres_local_table) + SELECT count(*) FROM cte_mixes; +EXECUTE mixed_query(1,2,3); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE mixed_query(1,2,3); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE mixed_query(1,2,3); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE mixed_query(1,2,3); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE mixed_query(1,2,3); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE mixed_query(1,2,3); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE mixed_query(1,2,3); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +-- anonymous columns +WITH a AS (SELECT a, '' FROM citus_local_table GROUP BY a) SELECT a.a FROM a ORDER BY 1 LIMIT 5; +NOTICE: executing the command locally: SELECT a FROM (SELECT citus_local_table.a, ''::text FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table GROUP BY citus_local_table.a) a(a, "?column?") ORDER BY a LIMIT 5 + a +--------------------------------------------------------------------- + 0 + 1 + 2 + 3 + 4 +(5 rows) + +WITH a AS (SELECT b, '' FROM citus_local_table WHERE a = 1) SELECT * FROM a, a b ORDER BY 1 LIMIT 5; +NOTICE: executing the command locally: WITH a AS (SELECT citus_local_table.b, ''::text FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (citus_local_table.a OPERATOR(pg_catalog.=) 1)) SELECT a.b, a."?column?", b.b, b."?column?" FROM a a(b, "?column?"), a b(b, "?column?") ORDER BY a.b LIMIT 5 + b | ?column? | b | ?column? +--------------------------------------------------------------------- + 1 | | 1 | +(1 row) + +-- weird expression on citus/pg table joins should be fine +SELECT * FROM citus_local_table, postgres_local_table +WHERE citus_local_table.a - postgres_local_table.a = 0 +ORDER BY 1,2,3,4 +LIMIT 10; +NOTICE: executing the command locally: SELECT citus_local_table.a, citus_local_table.b, postgres_local_table.a, postgres_local_table.b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table, citus_local_table_queries.postgres_local_table WHERE ((citus_local_table.a OPERATOR(pg_catalog.-) postgres_local_table.a) OPERATOR(pg_catalog.=) 0) ORDER BY citus_local_table.a, citus_local_table.b, postgres_local_table.a, postgres_local_table.b LIMIT 10 + a | b | a | b +--------------------------------------------------------------------- + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 + 2 | 2 | 2 | 2 + 3 | 3 | 3 | 3 + 4 | 4 | 4 | 4 + 5 | 5 | 5 | 5 +(6 rows) + +-- set operations should just work +SELECT * FROM citus_local_table UNION SELECT * FROM postgres_local_table UNION SELECT * FROM distributed_table ORDER BY 1,2; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +NOTICE: executing the command locally: 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) UNION SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ORDER BY 1, 2 + a | b +--------------------------------------------------------------------- + 0 | 0 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(6 rows) + +(SELECT * FROM citus_local_table ORDER BY 1,2 LIMIT 5) INTERSECT (SELECT i, i FROM generate_series(0, 100) i) ORDER BY 1, 2; +NOTICE: executing the command locally: (SELECT citus_local_table.a, citus_local_table.b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table ORDER BY citus_local_table.a, citus_local_table.b LIMIT 5) INTERSECT SELECT i.i, i.i FROM generate_series(0, 100) i(i) ORDER BY 1, 2 + a | b +--------------------------------------------------------------------- + 0 | 0 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 +(5 rows) + +-- should just work as recursive planner kicks in +SELECT count(*) FROM distributed_table WHERE a IN (SELECT a FROM citus_local_table); +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT count(*) FROM citus_local_table WHERE a IN (SELECT a FROM distributed_table); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table 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))) + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT count(*) FROM reference_table WHERE a IN (SELECT a FROM citus_local_table); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.reference_table_1509003 reference_table WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT citus_local_table.a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table)) + count +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT count(*) FROM citus_local_table WHERE a IN (SELECT a FROM reference_table); +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT reference_table.a FROM citus_local_table_queries.reference_table_1509003 reference_table)) + count +--------------------------------------------------------------------- + 6 +(1 row) + +-- nested recursive queries should just work +SELECT count(*) FROM citus_local_table + WHERE a IN + (SELECT a FROM distributed_table WHERE a IN + (SELECT b FROM citus_local_table WHERE b IN (SELECT b FROM postgres_local_table))); +NOTICE: executing the command locally: SELECT b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table 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))) +NOTICE: executing the command locally: SELECT count(*) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (a OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.a FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(a integer))) + count +--------------------------------------------------------------------- + 6 +(1 row) + +-- local outer joins +SELECT count(*) FROM citus_local_table LEFT JOIN reference_table ON (true); +NOTICE: executing the command locally: SELECT count(*) AS count FROM (citus_local_table_queries.citus_local_table_1509001 citus_local_table LEFT JOIN citus_local_table_queries.reference_table_1509003 reference_table ON (true)) + count +--------------------------------------------------------------------- + 36 +(1 row) + +SELECT count(*) FROM reference_table + LEFT JOIN citus_local_table ON (true) + LEFT JOIN postgres_local_table ON (true) + LEFT JOIN reference_table r2 ON (true); +NOTICE: executing the command locally: SELECT count(*) AS count FROM (((citus_local_table_queries.reference_table_1509003 reference_table LEFT JOIN citus_local_table_queries.citus_local_table_1509001 citus_local_table ON (true)) LEFT JOIN citus_local_table_queries.postgres_local_table ON (true)) LEFT JOIN citus_local_table_queries.reference_table_1509003 r2 ON (true)) + count +--------------------------------------------------------------------- + 1296 +(1 row) + +-- not supported direct outer join +SELECT count(*) FROM citus_local_table LEFT JOIN distributed_table ON (true); +ERROR: cannot pushdown the subquery +-- distinct in subquery on CTE +WITH one_row AS ( + SELECT a from citus_local_table WHERE b = 1 +) +SELECT + * +FROM + distributed_table +WHERE + b IN (SELECT DISTINCT a FROM one_row) +ORDER BY + 1, 2 +LIMIT + 1; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (b OPERATOR(pg_catalog.=) 1) + a | b +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +WITH one_row_2 AS ( + SELECT a from distributed_table WHERE b = 1 +) +SELECT + * +FROM + citus_local_table +WHERE + b IN (SELECT DISTINCT a FROM one_row_2) +ORDER BY + 1 ,2 +LIMIT + 1; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (b OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer))) ORDER BY a, b LIMIT 1 + a | b +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +-- join between citus local tables and distributed tables would fail +SELECT count(*) FROM citus_local_table, distributed_table; +NOTICE: executing the command locally: SELECT NULL::integer AS "dummy-1" FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true + count +--------------------------------------------------------------------- + 36 +(1 row) + +SELECT * FROM citus_local_table, distributed_table ORDER BY 1,2,3,4 FOR UPDATE; +ERROR: could not run distributed query with FOR UPDATE/SHARE commands +-- join between citus local tables and postgres local tables are okey +SELECT count(citus_local_table.b), count(postgres_local_table.a) +FROM citus_local_table, postgres_local_table +WHERE citus_local_table.a = postgres_local_table.b; +NOTICE: executing the command locally: SELECT count(citus_local_table.b) AS count, count(postgres_local_table.a) AS count FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table, citus_local_table_queries.postgres_local_table WHERE (citus_local_table.a OPERATOR(pg_catalog.=) postgres_local_table.b) + count | count +--------------------------------------------------------------------- + 6 | 6 +(1 row) + +-- select for update is just OK +SELECT * FROM citus_local_table ORDER BY 1,2 FOR UPDATE; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table ORDER BY a, b FOR UPDATE OF citus_local_table + a | b +--------------------------------------------------------------------- + 0 | 0 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(6 rows) + +--------------------------------------------------------------------- +----- INSERT SELECT ----- +--------------------------------------------------------------------- +-- simple INSERT SELECT is OK +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO citus_local_table +SELECT * from reference_table; +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) SELECT a, b FROM citus_local_table_queries.reference_table_1509003 reference_table +INSERT INTO reference_table +SELECT * from citus_local_table; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO citus_local_table +SELECT * from distributed_table; +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO distributed_table +SELECT * from citus_local_table; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +INSERT INTO citus_local_table +SELECT * from citus_local_table_2; +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) SELECT a, b FROM citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2 +INSERT INTO citus_local_table +SELECT * from citus_local_table_2 +ORDER BY 1,2 +LIMIT 10; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2 ORDER BY a, b LIMIT 10 +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO citus_local_table +SELECT * from postgres_local_table; +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO postgres_local_table +SELECT * from citus_local_table; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +-- INSERT SELECT with local joins are OK +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO citus_local_table +SELECT reference_table.* FROM reference_table +JOIN citus_local_table ON (true); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) SELECT reference_table.a, reference_table.b FROM (citus_local_table_queries.reference_table_1509003 reference_table JOIN citus_local_table_queries.citus_local_table_1509001 citus_local_table ON (true)) +INSERT INTO reference_table +SELECT reference_table.* FROM reference_table +JOIN citus_local_table ON (true); +NOTICE: executing the command locally: SELECT reference_table.a, reference_table.b FROM (citus_local_table_queries.reference_table_1509003 reference_table JOIN citus_local_table_queries.citus_local_table_1509001 citus_local_table ON (true)) +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO reference_table +SELECT reference_table.* FROM reference_table, postgres_local_table +JOIN citus_local_table ON (true); +NOTICE: executing the command locally: SELECT reference_table.a, reference_table.b FROM citus_local_table_queries.reference_table_1509003 reference_table, (citus_local_table_queries.postgres_local_table JOIN citus_local_table_queries.citus_local_table_1509001 citus_local_table ON (true)) +NOTICE: executing the copy locally for shard xxxxx +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO distributed_table +SELECT reference_table.* FROM reference_table +JOIN citus_local_table ON (true); +NOTICE: executing the command locally: SELECT reference_table.a, reference_table.b FROM (citus_local_table_queries.reference_table_1509003 reference_table JOIN citus_local_table_queries.citus_local_table_1509001 citus_local_table ON (true)) +INSERT INTO distributed_table +SELECT reference_table.* FROM reference_table, postgres_local_table +JOIN citus_local_table ON (true); +NOTICE: executing the command locally: SELECT reference_table.a, reference_table.b FROM citus_local_table_queries.reference_table_1509003 reference_table, (citus_local_table_queries.postgres_local_table JOIN citus_local_table_queries.citus_local_table_1509001 citus_local_table ON (true)) +INSERT INTO postgres_local_table +SELECT reference_table.* FROM reference_table +JOIN citus_local_table ON (true); +NOTICE: executing the command locally: SELECT reference_table.a, reference_table.b FROM (citus_local_table_queries.reference_table_1509003 reference_table JOIN citus_local_table_queries.citus_local_table_1509001 citus_local_table ON (true)) +-- INSERT SELECT that joins reference and distributed tables is also OK +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO citus_local_table +SELECT reference_table.* FROM reference_table +JOIN distributed_table ON (true); +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO citus_local_table +SELECT reference_table.* +FROM reference_table, distributed_table; +NOTICE: executing the copy locally for shard xxxxx +-- INSERT SELECT that joins citus local and distributed table directly will fail .. +INSERT INTO citus_local_table +SELECT distributed_table.* FROM distributed_table +JOIN citus_local_table ON (true); +NOTICE: executing the command locally: SELECT NULL::integer AS "dummy-1" FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true +NOTICE: executing the copy locally for shard xxxxx +-- .. but when wrapped into a CTE, join works fine +INSERT INTO citus_local_table +SELECT distributed_table.* FROM distributed_table +JOIN (WITH cte AS (SELECT * FROM citus_local_table) SELECT * FROM cte) as foo ON (true); +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +NOTICE: executing the copy locally for shard xxxxx +-- multi row insert is OK +INSERT INTO citus_local_table VALUES (1, 2), (3, 4); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a, b) VALUES (1,2), (3,4) +--------------------------------------------------------------------- +----- DELETE / UPDATE ----- +--------------------------------------------------------------------- +-- modifications using citus local tables and postgres local tables +-- are not supported, see below four tests +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +DELETE FROM citus_local_table +USING postgres_local_table +WHERE citus_local_table.b = postgres_local_table.b; +NOTICE: executing the command locally: DELETE FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table USING citus_local_table_queries.postgres_local_table WHERE (citus_local_table.b OPERATOR(pg_catalog.=) postgres_local_table.b) +UPDATE citus_local_table +SET b = 5 +FROM postgres_local_table +WHERE citus_local_table.a = 3 AND citus_local_table.b = postgres_local_table.b; +NOTICE: executing the command locally: UPDATE citus_local_table_queries.citus_local_table_1509001 citus_local_table SET b = 5 FROM citus_local_table_queries.postgres_local_table WHERE ((citus_local_table.a OPERATOR(pg_catalog.=) 3) AND (citus_local_table.b OPERATOR(pg_catalog.=) postgres_local_table.b)) +DELETE FROM postgres_local_table +USING citus_local_table +WHERE citus_local_table.b = postgres_local_table.b; +NOTICE: executing the command locally: DELETE FROM citus_local_table_queries.postgres_local_table USING citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE (citus_local_table.b OPERATOR(pg_catalog.=) postgres_local_table.b) +UPDATE postgres_local_table +SET b = 5 +FROM citus_local_table +WHERE citus_local_table.a = 3 AND citus_local_table.b = postgres_local_table.b; +NOTICE: executing the command locally: UPDATE citus_local_table_queries.postgres_local_table SET b = 5 FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE ((citus_local_table.a OPERATOR(pg_catalog.=) 3) AND (citus_local_table.b OPERATOR(pg_catalog.=) postgres_local_table.b)) +-- no direct joins supported +UPDATE distributed_table +SET b = 6 +FROM citus_local_table +WHERE citus_local_table.a = distributed_table.a; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true +UPDATE reference_table +SET b = 6 +FROM citus_local_table +WHERE citus_local_table.a = reference_table.a; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true +NOTICE: executing the command locally: UPDATE citus_local_table_queries.reference_table_1509003 reference_table SET b = 6 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 WHERE (citus_local_table.a OPERATOR(pg_catalog.=) reference_table.a) +-- should not work, add HINT use CTEs +UPDATE citus_local_table +SET b = 6 +FROM distributed_table +WHERE citus_local_table.a = distributed_table.a; +NOTICE: executing the command locally: UPDATE citus_local_table_queries.citus_local_table_1509001 citus_local_table SET b = 6 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 WHERE (citus_local_table.a OPERATOR(pg_catalog.=) distributed_table.a) +-- should work, add HINT use CTEs +UPDATE citus_local_table +SET b = 6 +FROM reference_table +WHERE citus_local_table.a = reference_table.a; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.reference_table_1509003 reference_table WHERE true +NOTICE: executing the command locally: UPDATE citus_local_table_queries.citus_local_table_1509001 citus_local_table SET b = 6 FROM (SELECT reference_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)) reference_table_1) reference_table WHERE (citus_local_table.a OPERATOR(pg_catalog.=) reference_table.a) +-- should not work, add HINT use CTEs +DELETE FROM distributed_table +USING citus_local_table +WHERE citus_local_table.a = distributed_table.a; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true +-- should not work, add HINT use CTEs +DELETE FROM citus_local_table +USING distributed_table +WHERE citus_local_table.a = distributed_table.a; +NOTICE: executing the command locally: DELETE FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table USING (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 WHERE (citus_local_table.a OPERATOR(pg_catalog.=) distributed_table.a) +DELETE FROM reference_table +USING citus_local_table +WHERE citus_local_table.a = reference_table.a; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true +NOTICE: executing the command locally: DELETE FROM citus_local_table_queries.reference_table_1509003 reference_table USING (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 WHERE (citus_local_table.a OPERATOR(pg_catalog.=) reference_table.a) +-- should work, add HINT use CTEs +DELETE FROM citus_local_table +USING reference_table +WHERE citus_local_table.a = reference_table.a; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.reference_table_1509003 reference_table WHERE true +NOTICE: executing the command locally: DELETE FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table USING (SELECT reference_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)) reference_table_1) reference_table WHERE (citus_local_table.a OPERATOR(pg_catalog.=) reference_table.a) +-- just works +DELETE FROM citus_local_table +WHERE citus_local_table.a IN (SELECT a FROM distributed_table); +NOTICE: executing the command locally: DELETE FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table 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))) +-- just works +DELETE FROM citus_local_table +WHERE citus_local_table.a IN (SELECT a FROM reference_table); +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.reference_table_1509003 reference_table +NOTICE: executing the command locally: DELETE FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table 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))) +-- just works +WITH distributed_table_cte AS (SELECT * FROM distributed_table) +UPDATE citus_local_table +SET b = 6 +FROM distributed_table_cte +WHERE citus_local_table.a = distributed_table_cte.a; +NOTICE: executing the command locally: UPDATE citus_local_table_queries.citus_local_table_1509001 citus_local_table SET b = 6 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)) distributed_table_cte WHERE (citus_local_table.a OPERATOR(pg_catalog.=) distributed_table_cte.a) +SET citus.log_local_commands to off; +-- just works +WITH reference_table_cte AS (SELECT * FROM reference_table) +UPDATE citus_local_table +SET b = 6 +FROM reference_table_cte +WHERE citus_local_table.a = reference_table_cte.a; +set citus.log_local_commands to on; +--------------------------------------------------------------------- +----- VIEW QUERIES ----- +--------------------------------------------------------------------- +CREATE MATERIALIZED VIEW mat_view_4 AS +SELECT count(*) +FROM citus_local_table +JOIN reference_table +USING (a); +NOTICE: executing the command locally: SELECT count(*) AS count FROM (citus_local_table_queries.citus_local_table_1509001 citus_local_table(a, b) JOIN citus_local_table_queries.reference_table_1509003 reference_table(a, b) USING (a)) +-- ok +SELECT count(*) FROM mat_view_4; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- should work +SELECT count(*) FROM distributed_table WHERE b in +(SELECT count FROM mat_view_4); + count +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE VIEW view_2 AS +SELECT count(*) +FROM citus_local_table +JOIN citus_local_table_2 USING (a) +JOIN distributed_table USING (a); +-- should fail as view contains direct local dist join +SELECT count(*) FROM view_2; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table WHERE true +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2 WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) view_2 + count +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE VIEW view_3 +AS SELECT count(*) +FROM citus_local_table_2 +JOIN reference_table +USING (a); +-- ok +SELECT count(*) FROM view_3; +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT count(*) AS count FROM (citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2(a, b) JOIN citus_local_table_queries.reference_table_1509003 reference_table(a, b) USING (a))) view_3 + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- view treated as subquery, so should work +SELECT count(*) FROM view_3, distributed_table; +NOTICE: executing the command locally: SELECT a FROM citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2 WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM ((SELECT citus_local_table_2_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_2_1) citus_local_table_2 JOIN citus_local_table_queries.reference_table_1509003 reference_table(a, b) USING (a)) + count +--------------------------------------------------------------------- + 6 +(1 row) + +--------------------------------------------------------------------- +-- Some other tests with subqueries & CTE's -- +--------------------------------------------------------------------- +SELECT clear_and_init_test_tables(); + clear_and_init_test_tables +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) AS a, count(*) AS b +FROM reference_table +JOIN (SELECT count(*) as a, count(*) as b + FROM citus_local_table_2 + JOIN (SELECT count(*) as a, count(*) as b + FROM postgres_local_table + JOIN (SELECT count(*) as a, count(*) as b + FROM reference_table as table_4677) subquery5108 + USING (a)) subquery7132 + USING (b)) subquery7294 +USING (a); +NOTICE: executing the command locally: SELECT count(*) AS a, count(*) AS b FROM (citus_local_table_queries.reference_table_1509003 reference_table(a, b) JOIN (SELECT count(*) AS a, count(*) AS b FROM (citus_local_table_queries.citus_local_table_2_1509002 citus_local_table_2(a, b) JOIN (SELECT count(*) AS a, count(*) AS b FROM (citus_local_table_queries.postgres_local_table JOIN (SELECT count(*) AS a, count(*) AS b FROM citus_local_table_queries.reference_table_1509003 table_4677) subquery5108 USING (a))) subquery7132 USING (b))) subquery7294 USING (a)) + a | b +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +-- direct join inside CTE not supported +WITH cte AS ( +UPDATE citus_local_table lt SET a = mt.a +FROM distributed_table mt WHERE mt.b = lt.b +RETURNING lt.b, lt.a +) SELECT * FROM cte JOIN distributed_table mt ON mt.b = cte.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: UPDATE citus_local_table_queries.citus_local_table_1509001 lt SET a = mt.a FROM (SELECT mt_1.a, mt_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)) mt_1) mt WHERE (mt.b OPERATOR(pg_catalog.=) lt.b) RETURNING lt.b, lt.a + b | a | a | b +--------------------------------------------------------------------- + 0 | 0 | 0 | 0 + 1 | 1 | 1 | 1 + 2 | 2 | 2 | 2 + 3 | 3 | 3 | 3 + 4 | 4 | 4 | 4 + 5 | 5 | 5 | 5 +(6 rows) + +-- join with CTE just works +UPDATE citus_local_table +SET a=5 +FROM (SELECT avg(distributed_table.b) as avg_b + FROM distributed_table) as foo +WHERE +foo.avg_b = citus_local_table.b; +NOTICE: executing the command locally: UPDATE citus_local_table_queries.citus_local_table_1509001 citus_local_table SET a = 5 FROM (SELECT intermediate_result.avg_b FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(avg_b numeric)) foo WHERE (foo.avg_b OPERATOR(pg_catalog.=) (citus_local_table.b)::numeric) +-- should work +UPDATE distributed_table +SET b = avg_a +FROM (SELECT avg(citus_local_table.a) as avg_a FROM citus_local_table) as foo +WHERE foo.avg_a = distributed_table.a +RETURNING distributed_table.*; +NOTICE: executing the command locally: SELECT avg(a) AS avg_a FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table + a | b +--------------------------------------------------------------------- +(0 rows) + +-- it is unfortunate that recursive planner cannot detect this +-- but expected to not work +UPDATE citus_local_table +SET a=5 +FROM (SELECT b FROM distributed_table) AS foo +WHERE foo.b = citus_local_table.b; +ERROR: local table citus_local_table cannot be joined with these distributed tables +--------------------------------------------------------------------- +-- test different execution paths -- +--------------------------------------------------------------------- +-- a bit different explain output than for postgres local tables +EXPLAIN (COSTS FALSE) +INSERT INTO citus_local_table +SELECT * FROM distributed_table +ORDER BY distributed_table.* +LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Limit + -> Sort + Sort Key: remote_scan.worker_column_3 + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Limit + -> Sort + Sort Key: distributed_table.* + -> Seq Scan on distributed_table_1509004 distributed_table +(14 rows) + +-- show that we do not pull to coordinator +EXPLAIN (COSTS FALSE) +INSERT INTO citus_local_table +SELECT * FROM citus_local_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Insert on citus_local_table_1509001 citus_table_alias + -> Seq Scan on citus_local_table_1509001 citus_local_table +(7 rows) + +EXPLAIN (COSTS FALSE) +INSERT INTO citus_local_table +SELECT reference_table.* FROM reference_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Insert on citus_local_table_1509001 citus_table_alias + -> Seq Scan on reference_table_1509003 reference_table +(7 rows) + +EXPLAIN (COSTS FALSE) +INSERT INTO citus_local_table +SELECT reference_table.* FROM reference_table, postgres_local_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Nested Loop + -> Seq Scan on reference_table_1509003 reference_table + -> Materialize + -> Seq Scan on postgres_local_table +(11 rows) + +-- show that we pull to coordinator when a distributed table is involved +EXPLAIN (COSTS FALSE) +INSERT INTO citus_local_table +SELECT reference_table.* FROM reference_table, distributed_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Nested Loop + -> Seq Scan on distributed_table_1509004 distributed_table + -> Materialize + -> Seq Scan on reference_table_1509003 reference_table +(11 rows) + +-- truncate tables & add unique constraints to be able to define foreign keys +TRUNCATE reference_table, citus_local_table, distributed_table; +NOTICE: executing the command locally: TRUNCATE TABLE citus_local_table_queries.reference_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE citus_local_table_queries.citus_local_table_xxxxx CASCADE +ALTER TABLE reference_table ADD CONSTRAINT pkey_ref PRIMARY KEY (a); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1509003, 'citus_local_table_queries', 'ALTER TABLE reference_table ADD CONSTRAINT pkey_ref PRIMARY KEY (a);') +ALTER TABLE citus_local_table ADD CONSTRAINT pkey_c PRIMARY KEY (a); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1509001, 'citus_local_table_queries', 'ALTER TABLE citus_local_table ADD CONSTRAINT pkey_c PRIMARY KEY (a);') +-- define a foreign key chain distributed table -> reference table -> citus local table +-- to test sequential execution +ALTER TABLE distributed_table ADD CONSTRAINT fkey_dist_to_ref FOREIGN KEY(a) REFERENCES reference_table(a) ON DELETE RESTRICT; +ALTER TABLE reference_table ADD CONSTRAINT fkey_ref_to_local FOREIGN KEY(a) REFERENCES citus_local_table(a) ON DELETE RESTRICT; +NOTICE: executing the command locally: SELECT worker_apply_inter_shard_ddl_command (1509003, 'citus_local_table_queries', 1509001, 'citus_local_table_queries', 'ALTER TABLE reference_table ADD CONSTRAINT fkey_ref_to_local FOREIGN KEY(a) REFERENCES citus_local_table(a) ON DELETE RESTRICT;') +INSERT INTO citus_local_table VALUES (1); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 (a) VALUES (1) +INSERT INTO reference_table VALUES (1); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.reference_table_1509003 (a) VALUES (1) +BEGIN; + INSERT INTO citus_local_table VALUES (1) ON CONFLICT (a) DO NOTHING; +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a) VALUES (1) ON CONFLICT(a) DO NOTHING + INSERT INTO distributed_table VALUES (1); + -- should show sequential as first inserting into citus local table + -- would force the xact block to use sequential execution + show citus.multi_shard_modify_mode; + citus.multi_shard_modify_mode +--------------------------------------------------------------------- + sequential +(1 row) + +ROLLBACK; +BEGIN; + TRUNCATE distributed_table; + -- should error out as we truncated distributed_table via parallel execution + TRUNCATE citus_local_table CASCADE; +NOTICE: truncate cascades to table "reference_table" +NOTICE: truncate cascades to table "distributed_table" +NOTICE: executing the command locally: TRUNCATE TABLE citus_local_table_queries.citus_local_table_xxxxx CASCADE +ERROR: cannot execute DDL on table "citus_local_table" because there was a parallel DDL access to distributed table "distributed_table" in the same transaction +ROLLBACK; +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + TRUNCATE distributed_table; + -- should work fine as we already switched to sequential execution + -- before parallel truncate + TRUNCATE citus_local_table CASCADE; +NOTICE: truncate cascades to table "reference_table" +NOTICE: truncate cascades to table "distributed_table" +NOTICE: executing the command locally: TRUNCATE TABLE citus_local_table_queries.citus_local_table_xxxxx CASCADE +NOTICE: truncate cascades to table "reference_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE citus_local_table_queries.reference_table_xxxxx CASCADE +ROLLBACK; +ALTER TABLE distributed_table DROP CONSTRAINT fkey_dist_to_ref; +BEGIN; + INSERT INTO citus_local_table VALUES (1) ON CONFLICT (a) DO NOTHING; +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a) VALUES (1) ON CONFLICT(a) DO NOTHING + show citus.multi_shard_modify_mode; + citus.multi_shard_modify_mode +--------------------------------------------------------------------- + sequential +(1 row) + +ROLLBACK; +-- remove uniqueness constraint and dependent foreign key constraint for next tests +ALTER TABLE reference_table DROP CONSTRAINT fkey_ref_to_local; +NOTICE: executing the command locally: SELECT worker_apply_inter_shard_ddl_command (1509003, 'citus_local_table_queries', 1509001, 'citus_local_table_queries', 'ALTER TABLE reference_table DROP CONSTRAINT fkey_ref_to_local;') +ALTER TABLE citus_local_table DROP CONSTRAINT pkey_c; +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1509001, 'citus_local_table_queries', 'ALTER TABLE citus_local_table DROP CONSTRAINT pkey_c;') +COPY citus_local_table(a) FROM PROGRAM 'seq 1'; +-- should use local execution +BEGIN; + COPY citus_local_table(a) FROM PROGRAM 'seq 1'; +NOTICE: executing the copy locally for shard xxxxx + COPY citus_local_table(a) FROM PROGRAM 'seq 1'; +NOTICE: executing the copy locally for shard xxxxx +COMMIT; +COPY citus_local_table TO STDOUT; +1 \N +1 \N +1 \N +1 \N +COPY (SELECT * FROM citus_local_table) TO STDOUT; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +1 \N +1 \N +1 \N +1 \N +BEGIN; + COPY citus_local_table TO STDOUT; +1 \N +1 \N +1 \N +1 \N +COMMIT; +BEGIN; + COPY (SELECT * FROM citus_local_table) TO STDOUT; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table +1 \N +1 \N +1 \N +1 \N +COMMIT; +-- truncate test tables for next test +TRUNCATE citus_local_table, reference_table, distributed_table; +NOTICE: executing the command locally: TRUNCATE TABLE citus_local_table_queries.citus_local_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE citus_local_table_queries.reference_table_xxxxx CASCADE +BEGIN; + INSERT INTO citus_local_table VALUES (1), (2); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a) VALUES (1), (2) + SAVEPOINT sp1; + INSERT INTO citus_local_table VALUES (3), (4); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a) VALUES (3), (4) + ROLLBACK TO SAVEPOINT sp1; + SELECT * FROM citus_local_table ORDER BY 1,2; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table ORDER BY a, b + a | b +--------------------------------------------------------------------- + 1 | + 2 | +(2 rows) + + SAVEPOINT sp2; + INSERT INTO citus_local_table VALUES (3), (4); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a) VALUES (3), (4) + INSERT INTO distributed_table VALUES (3), (4); + ROLLBACK TO SAVEPOINT sp2; + SELECT * FROM citus_local_table ORDER BY 1,2; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table ORDER BY a, b + a | b +--------------------------------------------------------------------- + 1 | + 2 | +(2 rows) + + SELECT * FROM distributed_table ORDER BY 1,2; + a | b +--------------------------------------------------------------------- +(0 rows) + + SAVEPOINT sp3; + INSERT INTO citus_local_table VALUES (3), (2); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.citus_local_table_1509001 AS citus_table_alias (a) VALUES (3), (2) + INSERT INTO reference_table VALUES (3), (2); +NOTICE: executing the command locally: INSERT INTO citus_local_table_queries.reference_table_1509003 AS citus_table_alias (a) VALUES (3), (2) + ROLLBACK TO SAVEPOINT sp3; + SELECT * FROM citus_local_table ORDER BY 1,2; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.citus_local_table_1509001 citus_local_table ORDER BY a, b + a | b +--------------------------------------------------------------------- + 1 | + 2 | +(2 rows) + + SELECT * FROM reference_table ORDER BY 1,2; +NOTICE: executing the command locally: SELECT a, b FROM citus_local_table_queries.reference_table_1509003 reference_table ORDER BY a, b + a | b +--------------------------------------------------------------------- +(0 rows) + +COMMIT; +-- cleanup at exit +DROP SCHEMA citus_local_table_queries CASCADE; +NOTICE: drop cascades to 14 other objects diff --git a/src/test/regress/expected/citus_split_shard_columnar_partitioned.out b/src/test/regress/expected/citus_split_shard_columnar_partitioned.out index 9c6d51457..29d1ab574 100644 --- a/src/test/regress/expected/citus_split_shard_columnar_partitioned.out +++ b/src/test/regress/expected/citus_split_shard_columnar_partitioned.out @@ -89,7 +89,7 @@ SET citus.shard_replication_factor TO 1; INNER JOIN pg_catalog.pg_class cls ON shard.logicalrelid = cls.oid INNER JOIN pg_catalog.pg_namespace ns ON cls.relnamespace = ns.oid WHERE node.noderole = 'primary' AND ns.nspname = 'citus_split_test_schema_columnar_partitioned' - ORDER BY logicalrelid, shardminvalue::BIGINT; + ORDER BY logicalrelid, shardminvalue::BIGINT, nodeport; shardid | logicalrelid | shardminvalue | shardmaxvalue | nodename | nodeport --------------------------------------------------------------------- 8970000 | sensors | -2147483648 | 2147483647 | localhost | 57637 @@ -273,7 +273,7 @@ SET citus.shard_replication_factor TO 1; INNER JOIN pg_catalog.pg_class cls ON shard.logicalrelid = cls.oid INNER JOIN pg_catalog.pg_namespace ns ON cls.relnamespace = ns.oid WHERE node.noderole = 'primary' AND ns.nspname = 'citus_split_test_schema_columnar_partitioned' - ORDER BY logicalrelid, shardminvalue::BIGINT; + ORDER BY logicalrelid, shardminvalue::BIGINT, nodeport; shardid | logicalrelid | shardminvalue | shardmaxvalue | nodename | nodeport --------------------------------------------------------------------- 8999000 | sensors | -2147483648 | -2120000000 | localhost | 57637 @@ -567,7 +567,7 @@ SET citus.shard_replication_factor TO 1; INNER JOIN pg_catalog.pg_class cls ON shard.logicalrelid = cls.oid INNER JOIN pg_catalog.pg_namespace ns ON cls.relnamespace = ns.oid WHERE node.noderole = 'primary' AND ns.nspname = 'citus_split_test_schema_columnar_partitioned' - ORDER BY logicalrelid, shardminvalue::BIGINT; + ORDER BY logicalrelid, shardminvalue::BIGINT, nodeport; shardid | logicalrelid | shardminvalue | shardmaxvalue | nodename | nodeport --------------------------------------------------------------------- 8999100 | sensors | -2147483648 | -2127770000 | localhost | 57637 diff --git a/src/test/regress/expected/columnar_chunk_filtering.out b/src/test/regress/expected/columnar_chunk_filtering.out index 0d0534ccc..292d5fb1a 100644 --- a/src/test/regress/expected/columnar_chunk_filtering.out +++ b/src/test/regress/expected/columnar_chunk_filtering.out @@ -264,17 +264,21 @@ EXPLAIN (analyze on, costs off, timing off, summary off) Columnar Projected Columns: a (9 rows) +SELECT plan_without_arrows($Q$ EXPLAIN (costs off, timing off, summary off) SELECT y, * FROM another_columnar_table; - QUERY PLAN +$Q$); + plan_without_arrows --------------------------------------------------------------------- Custom Scan (ColumnarScan) on another_columnar_table Columnar Projected Columns: x, y (2 rows) +SELECT plan_without_arrows($Q$ EXPLAIN (costs off, timing off, summary off) SELECT *, x FROM another_columnar_table; - QUERY PLAN +$Q$); + plan_without_arrows --------------------------------------------------------------------- Custom Scan (ColumnarScan) on another_columnar_table Columnar Projected Columns: x, y diff --git a/src/test/regress/expected/columnar_citus_integration.out b/src/test/regress/expected/columnar_citus_integration.out index d1baced0c..8beb09edf 100644 --- a/src/test/regress/expected/columnar_citus_integration.out +++ b/src/test/regress/expected/columnar_citus_integration.out @@ -957,13 +957,16 @@ SELECT * FROM weird_col_explain; Columnar Projected Columns: "bbbbbbbbbbbbbbbbbbbbbbbbb\!bbbb'bbbbbbbbbbbbbbbbbbbbb''bbbbbbbb", "aaaaaaaaaaaa$aaaaaa$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'aaaaaaaa'$a'" (7 rows) +\set VERBOSITY terse +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS OFF, SUMMARY OFF) SELECT *, "bbbbbbbbbbbbbbbbbbbbbbbbb\!bbbb'bbbbbbbbbbbbbbbbbbbbb''bbbbbbbb" FROM weird_col_explain WHERE "bbbbbbbbbbbbbbbbbbbbbbbbb\!bbbb'bbbbbbbbbbbbbbbbbbbbb''bbbbbbbb" * 2 > "aaaaaaaaaaaa$aaaaaa$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'aaaaaaaa'$a'!"; +$Q$); NOTICE: identifier "aaaaaaaaaaaa$aaaaaa$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'aaaaaaaa'$a'!" will be truncated to "aaaaaaaaaaaa$aaaaaa$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'aaaaaaaa'$a'" - QUERY PLAN + plan_without_result_lines --------------------------------------------------------------------- Custom Scan (Citus Adaptive) Task Count: 4 @@ -975,6 +978,7 @@ NOTICE: identifier "aaaaaaaaaaaa$aaaaaa$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'aaaaaaaa Columnar Projected Columns: "bbbbbbbbbbbbbbbbbbbbbbbbb\!bbbb'bbbbbbbbbbbbbbbbbbbbb''bbbbbbbb", "aaaaaaaaaaaa$aaaaaa$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'aaaaaaaa'$a'" (8 rows) +\set VERBOSITY default -- should not project any columns EXPLAIN (COSTS OFF, SUMMARY OFF) SELECT COUNT(*) FROM weird_col_explain; diff --git a/src/test/regress/expected/columnar_memory.out b/src/test/regress/expected/columnar_memory.out index 117b8f900..865472da1 100644 --- a/src/test/regress/expected/columnar_memory.out +++ b/src/test/regress/expected/columnar_memory.out @@ -71,10 +71,10 @@ write_clear_outside_xact | t INSERT INTO t SELECT i, 'last batch', 0 /* no need to record memusage per row */ FROM generate_series(1, 50000) i; -SELECT 1.0 * TopMemoryContext / :top_post BETWEEN 0.98 AND 1.02 AS top_growth_ok +SELECT CASE WHEN 1.0 * TopMemoryContext / :top_post BETWEEN 0.98 AND 1.03 THEN 1 ELSE 1.0 * TopMemoryContext / :top_post END AS top_growth FROM columnar_test_helpers.columnar_store_memory_stats(); --[ RECORD 1 ]-+-- -top_growth_ok | t +-[ RECORD 1 ]- +top_growth | 1 -- before this change, max mem usage while executing inserts was 28MB and -- with this change it's less than 8MB. diff --git a/src/test/regress/expected/columnar_permissions.out b/src/test/regress/expected/columnar_permissions.out index 5749a7458..d8b70d830 100644 --- a/src/test/regress/expected/columnar_permissions.out +++ b/src/test/regress/expected/columnar_permissions.out @@ -37,7 +37,8 @@ ERROR: must be owner of table no_access -- only tuples related to columnar_permissions should be visible select relation, chunk_group_row_limit, stripe_row_limit, compression, compression_level from columnar.options - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation; relation | chunk_group_row_limit | stripe_row_limit | compression | compression_level --------------------------------------------------------------------- columnar_permissions | 10000 | 2222 | none | 3 @@ -45,7 +46,8 @@ select relation, chunk_group_row_limit, stripe_row_limit, compression, compressi select relation, stripe_num, row_count, first_row_number from columnar.stripe - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; relation | stripe_num | row_count | first_row_number --------------------------------------------------------------------- columnar_permissions | 1 | 1 | 1 @@ -54,7 +56,8 @@ 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); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; relation | stripe_num | attr_num | chunk_group_num | value_count --------------------------------------------------------------------- columnar_permissions | 1 | 1 | 0 | 1 @@ -63,7 +66,8 @@ select relation, stripe_num, attr_num, chunk_group_num, value_count select relation, stripe_num, chunk_group_num, row_count from columnar.chunk_group - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; relation | stripe_num | chunk_group_num | row_count --------------------------------------------------------------------- columnar_permissions | 1 | 0 | 1 @@ -93,7 +97,8 @@ PL/pgSQL function alter_columnar_table_set(regclass,integer,integer,name,integer -- should see tuples from both columnar_permissions and no_access select relation, chunk_group_row_limit, stripe_row_limit, compression, compression_level from columnar.options - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation; relation | chunk_group_row_limit | stripe_row_limit | compression | compression_level --------------------------------------------------------------------- no_access | 10000 | 150000 | zstd | 3 @@ -102,7 +107,8 @@ select relation, chunk_group_row_limit, stripe_row_limit, compression, compressi select relation, stripe_num, row_count, first_row_number from columnar.stripe - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; relation | stripe_num | row_count | first_row_number --------------------------------------------------------------------- no_access | 1 | 1 | 1 @@ -116,7 +122,8 @@ 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); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; relation | stripe_num | attr_num | chunk_group_num | value_count --------------------------------------------------------------------- no_access | 1 | 1 | 0 | 1 @@ -134,7 +141,8 @@ select relation, stripe_num, attr_num, chunk_group_num, value_count select relation, stripe_num, chunk_group_num, row_count from columnar.chunk_group - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; relation | stripe_num | chunk_group_num | row_count --------------------------------------------------------------------- no_access | 1 | 0 | 1 diff --git a/src/test/regress/expected/coordinator_shouldhaveshards.out b/src/test/regress/expected/coordinator_shouldhaveshards.out index dd93dad39..5552a9503 100644 --- a/src/test/regress/expected/coordinator_shouldhaveshards.out +++ b/src/test/regress/expected/coordinator_shouldhaveshards.out @@ -1,4 +1,19 @@ +-- +-- COORDINATOR_SHOULDHAVESHARDS +-- -- Test queries on a distributed table with shards on the coordinator +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA coordinator_shouldhaveshards; SET search_path TO coordinator_shouldhaveshards; SET citus.next_shard_id TO 1503000; @@ -113,8 +128,8 @@ NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1 INSERT INTO repart_test (x, y) SELECT y, x FROM test; NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503000_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503000_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503000 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503003_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503003 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 -NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503004 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) -NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503007 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503004 AS citus_table_alias (x, y) SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503007 AS citus_table_alias (x, y) SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) SELECT y FROM repart_test WHERE x = 1000; y --------------------------------------------------------------------- @@ -124,8 +139,8 @@ SELECT y FROM repart_test WHERE x = 1000; INSERT INTO repart_test (x, y) SELECT y, x FROM test ON CONFLICT (x) DO UPDATE SET y = -1; NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503000_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503000_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503000 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503003_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503003 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 -NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503004 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = '-1'::integer -NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503007 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = '-1'::integer +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503004 AS citus_table_alias (x, y) SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = '-1'::integer +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503007 AS citus_table_alias (x, y) SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = '-1'::integer SELECT y FROM repart_test WHERE x = 1000; y --------------------------------------------------------------------- @@ -446,7 +461,7 @@ BEGIN; -- in postgres we wouldn't see this modifying cte, so it is consistent with postgres. WITH a AS (SELECT count(*) FROM test), b AS (INSERT INTO local VALUES (3,2) RETURNING *), c AS (INSERT INTO ref SELECT *,* FROM generate_series(1,10) RETURNING *), d AS (SELECT count(*) FROM ref JOIN local ON (a = x)) SELECT * FROM a, b, c, d ORDER BY x,y,a,b; NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_1503020'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_1503020'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) RETURNING citus_table_alias.a, citus_table_alias.b NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN (SELECT local_1.x, NULL::integer AS y FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) local_1) local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) @@ -610,7 +625,7 @@ INSERT INTO ref_table SELECT * FROM ref_table LIMIT 10000 ON CONFLICT (x) DO UPD SELECT count(*) FROM cte_1; NOTICE: executing the command locally: SELECT x, y FROM coordinator_shouldhaveshards.ref_table_1503039 ref_table LIMIT 10000 NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_table_1503039 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_result('insert_select_XXX_1503039'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = (excluded.y OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.x, citus_table_alias.y +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_table_1503039 AS citus_table_alias (x, y) SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('insert_select_XXX_1503039'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = (excluded.y OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.x, citus_table_alias.y NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) cte_1 count --------------------------------------------------------------------- @@ -923,7 +938,7 @@ inserts AS ( RETURNING * ) 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: 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: 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 diff --git a/src/test/regress/expected/coordinator_shouldhaveshards_0.out b/src/test/regress/expected/coordinator_shouldhaveshards_0.out new file mode 100644 index 000000000..1ee81631e --- /dev/null +++ b/src/test/regress/expected/coordinator_shouldhaveshards_0.out @@ -0,0 +1,1188 @@ +-- +-- COORDINATOR_SHOULDHAVESHARDS +-- +-- Test queries on a distributed table with shards on the coordinator +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA coordinator_shouldhaveshards; +SET search_path TO coordinator_shouldhaveshards; +SET citus.next_shard_id TO 1503000; +SET citus.next_placement_id TO 1503000; +-- idempotently add node to allow this test to run without add_coordinator +SET client_min_messages TO WARNING; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +RESET client_min_messages; +SELECT 1 FROM master_set_node_property('localhost', :master_port, 'shouldhaveshards', true); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SET citus.shard_replication_factor TO 1; +CREATE TABLE test (x int, y int); +SELECT create_distributed_table('test','x', colocate_with := 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) +WHERE logicalrelid = 'test'::regclass AND groupid = 0; + count +--------------------------------------------------------------------- + 2 +(1 row) + +--- enable logging to see which tasks are executed locally +SET client_min_messages TO LOG; +SET citus.log_local_commands TO ON; +-- INSERT..SELECT with COPY under the covers +INSERT INTO test SELECT s,s FROM generate_series(2,100) s; +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +-- router queries execute locally +INSERT INTO test VALUES (1, 1); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.test_1503000 (x, y) VALUES (1, 1) +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +-- multi-shard queries connect to localhost +SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 100 +(1 row) + +WITH a AS (SELECT * FROM test) SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 100 +(1 row) + +-- multi-shard queries in transaction blocks execute locally +BEGIN; +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +END; +BEGIN; +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +END; +-- INSERT..SELECT with re-partitioning after local execution +BEGIN; +INSERT INTO test VALUES (0,1000); +CREATE TABLE repart_test (x int primary key, y int); +SELECT create_distributed_table('repart_test','x', colocate_with := 'none'); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503004, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.repart_test (x integer NOT NULL, y integer) ');SELECT worker_apply_shard_ddl_command (1503004, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.repart_test OWNER TO postgres');SELECT worker_apply_shard_ddl_command (1503004, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.repart_test ADD CONSTRAINT repart_test_pkey PRIMARY KEY (x)') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503007, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.repart_test (x integer NOT NULL, y integer) ');SELECT worker_apply_shard_ddl_command (1503007, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.repart_test OWNER TO postgres');SELECT worker_apply_shard_ddl_command (1503007, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.repart_test ADD CONSTRAINT repart_test_pkey PRIMARY KEY (x)') + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO repart_test (x, y) SELECT y, x FROM test; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503000_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503000_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503000 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503003_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503003 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503004 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503007 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) +SELECT y FROM repart_test WHERE x = 1000; + y +--------------------------------------------------------------------- + 0 +(1 row) + +INSERT INTO repart_test (x, y) SELECT y, x FROM test ON CONFLICT (x) DO UPDATE SET y = -1; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503000_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503000_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503000 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503003_to','SELECT y AS x, x AS y FROM coordinator_shouldhaveshards.test_1503003 test WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503004 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = '-1'::integer +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.repart_test_1503007 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1503003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = '-1'::integer +SELECT y FROM repart_test WHERE x = 1000; + y +--------------------------------------------------------------------- + -1 +(1 row) + +ROLLBACK; +-- INSERT..SELECT with re-partitioning in EXPLAIN ANALYZE after local execution +BEGIN; +INSERT INTO test VALUES (0,1000); +EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE) INSERT INTO test (x, y) SELECT y, x FROM test; +ERROR: EXPLAIN ANALYZE is currently not supported for INSERT ... SELECT commands with repartitioning +ROLLBACK; +-- DDL connects to locahost +ALTER TABLE test ADD COLUMN z int; +-- DDL after local execution +BEGIN; +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +ALTER TABLE test DROP COLUMN z; +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503000, 'coordinator_shouldhaveshards', 'ALTER TABLE test DROP COLUMN z;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503003, 'coordinator_shouldhaveshards', 'ALTER TABLE test DROP COLUMN z;') +ROLLBACK; +BEGIN; +ALTER TABLE test DROP COLUMN z; +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503000, 'coordinator_shouldhaveshards', 'ALTER TABLE test DROP COLUMN z;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503003, 'coordinator_shouldhaveshards', 'ALTER TABLE test DROP COLUMN z;') +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +END; +SET citus.shard_count TO 6; +SET citus.log_remote_commands TO OFF; +BEGIN; +SET citus.log_local_commands TO ON; +CREATE TABLE dist_table (a int); +INSERT INTO dist_table SELECT * FROM generate_series(1, 100); +-- trigger local execution +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +-- this should be run locally +SELECT create_distributed_table('dist_table', 'a', colocate_with := 'none'); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503008, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table (a integer) ');SELECT worker_apply_shard_ddl_command (1503008, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503011, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table (a integer) ');SELECT worker_apply_shard_ddl_command (1503011, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table OWNER TO postgres') +NOTICE: executing the copy locally for shard xxxxx +NOTICE: Copying data from local table... +NOTICE: executing the copy locally for shard xxxxx +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($$coordinator_shouldhaveshards.dist_table$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) FROM dist_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.dist_table_1503008 dist_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.dist_table_1503011 dist_table WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +ROLLBACK; +CREATE TABLE dist_table (a int); +INSERT INTO dist_table SELECT * FROM generate_series(1, 100); +BEGIN; +SET citus.log_local_commands TO ON; +-- trigger local execution +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +-- this should be run locally +SELECT create_distributed_table('dist_table', 'a', colocate_with := 'none'); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503014, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table (a integer) ');SELECT worker_apply_shard_ddl_command (1503014, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503017, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table (a integer) ');SELECT worker_apply_shard_ddl_command (1503017, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table OWNER TO postgres') +NOTICE: executing the copy locally for shard xxxxx +NOTICE: Copying data from local table... +NOTICE: executing the copy locally for shard xxxxx +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($$coordinator_shouldhaveshards.dist_table$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) FROM dist_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.dist_table_1503014 dist_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.dist_table_1503017 dist_table WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +ROLLBACK; +-- repartition queries should work fine +SET citus.enable_repartition_joins TO ON; +SELECT count(*) FROM test t1, test t2 WHERE t1.x = t2.y; + count +--------------------------------------------------------------------- + 100 +(1 row) + +BEGIN; +SET citus.enable_unique_job_ids TO off; +SELECT count(*) FROM test t1, test t2 WHERE t1.x = t2.y; +NOTICE: executing the command locally: SELECT partition_index, 'repartition_25_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_25_1','SELECT x AS column1 FROM coordinator_shouldhaveshards.test_1503000 t1 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_25_4' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_25_4','SELECT x AS column1 FROM coordinator_shouldhaveshards.test_1503003 t1 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_26_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_26_1','SELECT y AS column1 FROM coordinator_shouldhaveshards.test_1503000 t2 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_26_4' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_26_4','SELECT y AS column1 FROM coordinator_shouldhaveshards.test_1503003 t2 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_1_0']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_2_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_3_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_4_0']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_1_0']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_2_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_3_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_4_0']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_1_3']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_2_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_3_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_25_4_3']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_1_3']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_2_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_3_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_26_4_3']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_25_1_0,repartition_25_2_0,repartition_25_3_0,repartition_25_4_0}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 integer) JOIN read_intermediate_results('{repartition_26_1_0,repartition_26_2_0,repartition_26_3_0,repartition_26_4_0}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 integer) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_25_1_3,repartition_25_2_3,repartition_25_3_3,repartition_25_4_3}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 integer) JOIN read_intermediate_results('{repartition_26_1_3,repartition_26_2_3,repartition_26_3_3,repartition_26_4_3}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 integer) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +END; +BEGIN; +SET citus.enable_repartition_joins TO ON; +-- trigger local execution +SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM test t1, test t2 WHERE t1.x = t2.y; +NOTICE: executing the command locally: SELECT partition_index, 'repartition_29_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_29_1','SELECT x AS column1 FROM coordinator_shouldhaveshards.test_1503000 t1 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_29_4' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_29_4','SELECT x AS column1 FROM coordinator_shouldhaveshards.test_1503003 t1 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_30_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_30_1','SELECT y AS column1 FROM coordinator_shouldhaveshards.test_1503000 t2 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_30_4' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_30_4','SELECT y AS column1 FROM coordinator_shouldhaveshards.test_1503003 t2 WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_1_2']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_2_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_3_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_4_2']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_1_2']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_2_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_3_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_4_2']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_1_5']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_2_5']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_3_5']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_29_4_5']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_1_5']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_2_5']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_3_5']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_30_4_5']::text[],'localhost',57636) bytes +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_29_1_2,repartition_29_2_2,repartition_29_3_2,repartition_29_4_2}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 integer) JOIN read_intermediate_results('{repartition_30_1_2,repartition_30_2_2,repartition_30_3_2,repartition_30_4_2}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 integer) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_29_1_5,repartition_29_2_5,repartition_29_3_5,repartition_29_4_5}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 integer) JOIN read_intermediate_results('{repartition_30_1_5,repartition_30_2_5,repartition_30_3_5,repartition_30_4_5}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 integer) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +ROLLBACK; +CREATE TABLE ref (a int, b int); +SELECT create_reference_table('ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE local (x int, y int); +BEGIN; +SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +SELECT * FROM ref JOIN local ON (a = x); +NOTICE: executing the command locally: SELECT ref.a, ref.b, local.x, local.y FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN coordinator_shouldhaveshards.local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) + a | b | x | y +--------------------------------------------------------------------- +(0 rows) + +TRUNCATE ref; +NOTICE: executing the command locally: TRUNCATE TABLE coordinator_shouldhaveshards.ref_xxxxx CASCADE +ROLLBACK; +BEGIN; +SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +TRUNCATE ref; +NOTICE: executing the command locally: TRUNCATE TABLE coordinator_shouldhaveshards.ref_xxxxx CASCADE +SELECT * FROM ref JOIN local ON (a = x); +NOTICE: executing the command locally: SELECT ref.a, ref.b, local.x, local.y FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN coordinator_shouldhaveshards.local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) + a | b | x | y +--------------------------------------------------------------------- +(0 rows) + +ROLLBACK; +BEGIN; +SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +INSERT INTO ref VALUES (1,2); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 (a, b) VALUES (1, 2) +INSERT INTO local VALUES (1,2); +SELECT * FROM ref JOIN local ON (a = x); +NOTICE: executing the command locally: SELECT ref.a, ref.b, local.x, local.y FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN coordinator_shouldhaveshards.local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) + a | b | x | y +--------------------------------------------------------------------- + 1 | 2 | 1 | 2 +(1 row) + +ROLLBACK; +BEGIN; +SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +-- we wont see the modifying cte in this query because we will use local execution and +-- in postgres we wouldn't see this modifying cte, so it is consistent with postgres. +WITH a AS (SELECT count(*) FROM test), b AS (INSERT INTO local VALUES (3,2) RETURNING *), c AS (INSERT INTO ref VALUES (3,2) RETURNING *), d AS (SELECT count(*) FROM ref JOIN local ON (a = x)) SELECT * FROM a, b, c, d ORDER BY x,y,a,b; +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 (a, b) VALUES (3, 2) RETURNING a, b +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN (SELECT local_1.x, NULL::integer AS y FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) local_1) local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) +NOTICE: executing the command locally: SELECT a.count, b.x, b.y, c.a, c.b, d.count FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) a, (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) b, (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) c, (SELECT intermediate_result.count FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) d ORDER BY b.x, b.y, c.a, c.b + count | x | y | a | b | count +--------------------------------------------------------------------- + 100 | 3 | 2 | 3 | 2 | 0 +(1 row) + +TRUNCATE ref; +NOTICE: executing the command locally: TRUNCATE TABLE coordinator_shouldhaveshards.ref_xxxxx CASCADE +SELECT * FROM ref JOIN local ON (a = x); +NOTICE: executing the command locally: SELECT ref.a, ref.b, local.x, local.y FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN coordinator_shouldhaveshards.local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) + a | b | x | y +--------------------------------------------------------------------- +(0 rows) + +-- we wont see the modifying cte in this query because we will use local execution and +-- in postgres we wouldn't see this modifying cte, so it is consistent with postgres. +WITH a AS (SELECT count(*) FROM test), b AS (INSERT INTO local VALUES (3,2) RETURNING *), c AS (INSERT INTO ref VALUES (3,2) RETURNING *), d AS (SELECT count(*) FROM ref JOIN local ON (a = x)) SELECT * FROM a, b, c, d ORDER BY x,y,a,b; +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 (a, b) VALUES (3, 2) RETURNING a, b +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN (SELECT local_1.x, NULL::integer AS y FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) local_1) local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) +NOTICE: executing the command locally: SELECT a.count, b.x, b.y, c.a, c.b, d.count FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) a, (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) b, (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) c, (SELECT intermediate_result.count FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) d ORDER BY b.x, b.y, c.a, c.b + count | x | y | a | b | count +--------------------------------------------------------------------- + 100 | 3 | 2 | 3 | 2 | 0 +(1 row) + +ROLLBACK; +BEGIN; +-- we wont see the modifying cte in this query because we will use local execution and +-- in postgres we wouldn't see this modifying cte, so it is consistent with postgres. +WITH a AS (SELECT count(*) FROM test), b AS (INSERT INTO local VALUES (3,2) RETURNING *), c AS (INSERT INTO ref VALUES (3,2) RETURNING *), d AS (SELECT count(*) FROM ref JOIN local ON (a = x)) SELECT * FROM a, b, c, d ORDER BY x,y,a,b; +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 (a, b) VALUES (3, 2) RETURNING a, b +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN (SELECT local_1.x, NULL::integer AS y FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) local_1) local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) +NOTICE: executing the command locally: SELECT a.count, b.x, b.y, c.a, c.b, d.count FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) a, (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) b, (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) c, (SELECT intermediate_result.count FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) d ORDER BY b.x, b.y, c.a, c.b + count | x | y | a | b | count +--------------------------------------------------------------------- + 100 | 3 | 2 | 3 | 2 | 0 +(1 row) + +ROLLBACK; +BEGIN; +-- we wont see the modifying cte in this query because we will use local execution and +-- in postgres we wouldn't see this modifying cte, so it is consistent with postgres. +WITH a AS (SELECT count(*) FROM test), b AS (INSERT INTO local VALUES (3,2) RETURNING *), c AS (INSERT INTO ref SELECT *,* FROM generate_series(1,10) RETURNING *), d AS (SELECT count(*) FROM ref JOIN local ON (a = x)) SELECT * FROM a, b, c, d ORDER BY x,y,a,b; +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_1503020'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN (SELECT local_1.x, NULL::integer AS y FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) local_1) local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) +NOTICE: executing the command locally: SELECT a.count, b.x, b.y, c.a, c.b, d.count FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) a, (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) b, (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) c, (SELECT intermediate_result.count FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) d ORDER BY b.x, b.y, c.a, c.b + count | x | y | a | b | count +--------------------------------------------------------------------- + 100 | 3 | 2 | 1 | 1 | 0 + 100 | 3 | 2 | 2 | 2 | 0 + 100 | 3 | 2 | 3 | 3 | 0 + 100 | 3 | 2 | 4 | 4 | 0 + 100 | 3 | 2 | 5 | 5 | 0 + 100 | 3 | 2 | 6 | 6 | 0 + 100 | 3 | 2 | 7 | 7 | 0 + 100 | 3 | 2 | 8 | 8 | 0 + 100 | 3 | 2 | 9 | 9 | 0 + 100 | 3 | 2 | 10 | 10 | 0 +(10 rows) + +ROLLBACK; +-- same local table reference table tests, but outside a transaction block +INSERT INTO ref VALUES (1,2); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 (a, b) VALUES (1, 2) +INSERT INTO local VALUES (1,2); +SELECT * FROM ref JOIN local ON (a = x); +NOTICE: executing the command locally: SELECT ref.a, ref.b, local.x, local.y FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN coordinator_shouldhaveshards.local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) + a | b | x | y +--------------------------------------------------------------------- + 1 | 2 | 1 | 2 +(1 row) + +-- we wont see the modifying cte in this query because we will use local execution and +-- in postgres we wouldn't see this modifying cte, so it is consistent with postgres. +WITH a AS (SELECT count(*) FROM test), b AS (INSERT INTO local VALUES (3,2) RETURNING *), c AS (INSERT INTO ref VALUES (3,2) RETURNING *), d AS (SELECT count(*) FROM ref JOIN local ON (a = x)) SELECT * FROM a, b, c, d ORDER BY x,y,a,b; +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_1503020 (a, b) VALUES (3, 2) RETURNING a, b +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.ref_1503020 ref JOIN (SELECT local_1.x, NULL::integer AS y FROM (SELECT intermediate_result.x FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) local_1) local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) +NOTICE: executing the command locally: SELECT a.count, b.x, b.y, c.a, c.b, d.count FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) a, (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) b, (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) c, (SELECT intermediate_result.count FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) d ORDER BY b.x, b.y, c.a, c.b + count | x | y | a | b | count +--------------------------------------------------------------------- + 100 | 3 | 2 | 3 | 2 | 1 +(1 row) + +-- joins between local tables and distributed tables are disallowed +CREATE TABLE dist_table(a int); +ERROR: relation "dist_table" already exists +SELECT create_distributed_table('dist_table', 'a'); +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($$coordinator_shouldhaveshards.dist_table$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO dist_table VALUES(1); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.dist_table_1503021 (a) VALUES (1) +SELECT * FROM local JOIN dist_table ON (a = x) ORDER BY 1,2,3; + x | y | a +--------------------------------------------------------------------- + 1 | 2 | 1 + 1 | 2 | 1 + 3 | 2 | 3 +(3 rows) + +SELECT * FROM local JOIN dist_table ON (a = x) WHERE a = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT local.x, local.y, dist_table.a FROM ((SELECT local_1.x, local_1.y FROM (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) local_1) local JOIN coordinator_shouldhaveshards.dist_table_1503021 dist_table ON ((dist_table.a OPERATOR(pg_catalog.=) local.x))) WHERE (dist_table.a OPERATOR(pg_catalog.=) 1) ORDER BY local.x, local.y, dist_table.a + x | y | a +--------------------------------------------------------------------- + 1 | 2 | 1 + 1 | 2 | 1 +(2 rows) + +-- intermediate results are allowed +WITH cte_1 AS (SELECT * FROM dist_table ORDER BY 1 LIMIT 1) +SELECT * FROM ref JOIN local ON (a = x) JOIN cte_1 ON (local.x = cte_1.a); +NOTICE: executing the command locally: SELECT a FROM coordinator_shouldhaveshards.dist_table_1503021 dist_table WHERE true ORDER BY a LIMIT '1'::bigint +NOTICE: executing the command locally: SELECT a FROM coordinator_shouldhaveshards.dist_table_1503024 dist_table WHERE true ORDER BY a LIMIT '1'::bigint +NOTICE: executing the command locally: SELECT ref.a, ref.b, local.x, local.y, cte_1.a FROM ((coordinator_shouldhaveshards.ref_1503020 ref JOIN (SELECT local_1.x, local_1.y FROM (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) local_1) local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) JOIN (SELECT intermediate_result.a FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer)) cte_1 ON ((local.x OPERATOR(pg_catalog.=) cte_1.a))) + a | b | x | y | a +--------------------------------------------------------------------- + 1 | 2 | 1 | 2 | 1 +(1 row) + +-- full router query with CTE and local +WITH cte_1 AS (SELECT * FROM ref LIMIT 1) +SELECT * FROM ref JOIN local ON (a = x) JOIN cte_1 ON (local.x = cte_1.a); +NOTICE: executing the command locally: SELECT ref.a, ref.b, local.x, local.y, cte_1.a, cte_1.b FROM ((coordinator_shouldhaveshards.ref_1503020 ref JOIN coordinator_shouldhaveshards.local ON ((ref.a OPERATOR(pg_catalog.=) local.x))) JOIN (SELECT ref_1.a, ref_1.b FROM coordinator_shouldhaveshards.ref_1503020 ref_1 LIMIT 1) cte_1 ON ((local.x OPERATOR(pg_catalog.=) cte_1.a))) + a | b | x | y | a | b +--------------------------------------------------------------------- + 1 | 2 | 1 | 2 | 1 | 2 +(1 row) + +DROP TABLE dist_table; +-- issue #3801 +SET citus.shard_replication_factor TO 2; +CREATE TABLE dist_table(a int); +SELECT create_distributed_table('dist_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +BEGIN; +-- this will use perPlacementQueryStrings, make sure it works correctly with +-- copying task +INSERT INTO dist_table SELECT a + 1 FROM dist_table; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503027_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503027_to','SELECT (a OPERATOR(pg_catalog.+) 1) AS a FROM coordinator_shouldhaveshards.dist_table_1503027 dist_table WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503029_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503029_to','SELECT (a OPERATOR(pg_catalog.+) 1) AS a FROM coordinator_shouldhaveshards.dist_table_1503029 dist_table WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503030_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503030_to','SELECT (a OPERATOR(pg_catalog.+) 1) AS a FROM coordinator_shouldhaveshards.dist_table_1503030 dist_table WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1503032_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1503032_to','SELECT (a OPERATOR(pg_catalog.+) 1) AS a FROM coordinator_shouldhaveshards.dist_table_1503032 dist_table WHERE true',0,'hash','{-2147483648,-1431655766,-715827884,-2,715827880,1431655762}'::text[],'{-1431655767,-715827885,-3,715827879,1431655761,2147483647}'::text[],true) WHERE rows_written > 0 +ROLLBACK; +SET citus.shard_replication_factor TO 1; +BEGIN; +SET citus.shard_replication_factor TO 2; +CREATE TABLE dist_table1(a int); +-- this will use queryStringList, make sure it works correctly with +-- copying task +SELECT create_distributed_table('dist_table1', 'a'); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503033, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table1 (a integer) ');SELECT worker_apply_shard_ddl_command (1503033, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table1 OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503035, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table1 (a integer) ');SELECT worker_apply_shard_ddl_command (1503035, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table1 OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503036, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table1 (a integer) ');SELECT worker_apply_shard_ddl_command (1503036, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table1 OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (1503038, 'coordinator_shouldhaveshards', 'CREATE TABLE coordinator_shouldhaveshards.dist_table1 (a integer) ');SELECT worker_apply_shard_ddl_command (1503038, 'coordinator_shouldhaveshards', 'ALTER TABLE coordinator_shouldhaveshards.dist_table1 OWNER TO postgres') + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ROLLBACK; +CREATE table ref_table(x int PRIMARY KEY, y int); +-- this will be replicated to the coordinator because of add_coordinator test +SELECT create_reference_table('ref_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +TRUNCATE TABLE test; +BEGIN; +INSERT INTO test SELECT *, * FROM generate_series(1, 100); +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO ref_table SELECT *, * FROM generate_series(1, 100); +NOTICE: executing the copy locally for shard xxxxx +SELECT COUNT(*) FROM test JOIN ref_table USING(x); +NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.test_1503000 test JOIN coordinator_shouldhaveshards.ref_table_1503039 ref_table ON ((test.x OPERATOR(pg_catalog.=) ref_table.x))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (coordinator_shouldhaveshards.test_1503003 test JOIN coordinator_shouldhaveshards.ref_table_1503039 ref_table ON ((test.x OPERATOR(pg_catalog.=) ref_table.x))) WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +ROLLBACK; +-- writing to local file and remote intermediate files +-- at the same time +INSERT INTO ref_table SELECT *, * FROM generate_series(1, 100); +NOTICE: executing the copy locally for shard xxxxx +WITH cte_1 AS ( +INSERT INTO ref_table SELECT * FROM ref_table LIMIT 10000 ON CONFLICT (x) DO UPDATE SET y = EXCLUDED.y + 1 RETURNING *) +SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: SELECT x, y FROM coordinator_shouldhaveshards.ref_table_1503039 ref_table LIMIT 10000 +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.ref_table_1503039 AS citus_table_alias (x, y) SELECT x, y FROM read_intermediate_result('insert_select_XXX_1503039'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) ON CONFLICT(x) DO UPDATE SET y = (excluded.y OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.x, citus_table_alias.y +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) cte_1 + count +--------------------------------------------------------------------- + 100 +(1 row) + +-- issue #4237: preventing empty placement creation on coordinator +CREATE TABLE test_append_table(a int); +SELECT create_distributed_table('test_append_table', 'a', 'append'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- this will fail since it will try to create an empty placement in the +-- coordinator as well +SET citus.shard_replication_factor TO 3; +SELECT master_create_empty_shard('test_append_table'); +NOTICE: Creating placements for the append partitioned tables on the coordinator is not supported, skipping coordinator ... +ERROR: could only create 2 of 3 of required shard replicas +-- this will create an empty shard with replicas in the two worker nodes +SET citus.shard_replication_factor TO 2; +SELECT 1 FROM master_create_empty_shard('test_append_table'); +NOTICE: Creating placements for the append partitioned tables on the coordinator is not supported, skipping coordinator ... + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- verify groupid is not 0 for each placement +SELECT COUNT(*) FROM pg_dist_placement p, pg_dist_shard s WHERE p.shardid = s.shardid AND s.logicalrelid = 'test_append_table'::regclass AND p.groupid > 0; + count +--------------------------------------------------------------------- + 2 +(1 row) + +SET citus.shard_replication_factor TO 1; +-- test partitioned index creation with long name +CREATE TABLE test_index_creation1 +( + tenant_id integer NOT NULL, + timeperiod timestamp without time zone NOT NULL, + field1 integer NOT NULL, + inserted_utc timestamp without time zone NOT NULL DEFAULT now(), + PRIMARY KEY(tenant_id, timeperiod) +) PARTITION BY RANGE (timeperiod); +CREATE TABLE test_index_creation1_p2020_09_26 +PARTITION OF test_index_creation1 FOR VALUES FROM ('2020-09-26 00:00:00') TO ('2020-09-27 00:00:00'); +CREATE TABLE test_index_creation1_p2020_09_27 +PARTITION OF test_index_creation1 FOR VALUES FROM ('2020-09-27 00:00:00') TO ('2020-09-28 00:00:00'); +select create_distributed_table('test_index_creation1', 'tenant_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- should be able to create indexes with INCLUDE/WHERE +CREATE INDEX ix_test_index_creation5 ON test_index_creation1 + USING btree(tenant_id, timeperiod) + INCLUDE (field1) WHERE (tenant_id = 100); +NOTICE: executing the command locally: CREATE INDEX ix_test_index_creation5_1503042 ON coordinator_shouldhaveshards.test_index_creation1_1503042 USING btree (tenant_id ,timeperiod ) INCLUDE (field1 ) WHERE (tenant_id = 100) +NOTICE: executing the command locally: CREATE INDEX ix_test_index_creation5_1503045 ON coordinator_shouldhaveshards.test_index_creation1_1503045 USING btree (tenant_id ,timeperiod ) INCLUDE (field1 ) WHERE (tenant_id = 100) +NOTICE: executing the command locally: SELECT pg_catalog.citus_run_local_command($$SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503048', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503048');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503049', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503049');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503050', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503050');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503051', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503051');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503052', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503052');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503053', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503053');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503054', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503054');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503055', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503055');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503056', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503056');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503057', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503057');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503058', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503058');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503042'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503059', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503059')$$) +NOTICE: executing the command locally: SELECT pg_catalog.citus_run_local_command($$SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503048', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503048');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503049', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503049');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503050', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503050');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503051', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503051');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503052', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503052');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_26_1503053', 'test_index_creation1_p2020_09_2_tenant_id_time_6020e8f8_1503053');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503054', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503054');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503055', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503055');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503056', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503056');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503057', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503057');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503058', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503058');SELECT worker_fix_partition_shard_index_names('coordinator_shouldhaveshards.ix_test_index_creation5_1503045'::regclass, 'coordinator_shouldhaveshards.test_index_creation1_p2020_09_27_1503059', 'test_index_creation1_p2020_09__tenant_id_timep_624f7e94_1503059')$$) +-- test if indexes are created +SELECT 1 AS created WHERE EXISTS(SELECT * FROM pg_indexes WHERE indexname LIKE '%test_index_creation%'); + created +--------------------------------------------------------------------- + 1 +(1 row) + +-- test alter_distributed_table UDF +SET citus.shard_count TO 4; +CREATE TABLE adt_table (a INT, b INT); +CREATE TABLE adt_col (a INT UNIQUE, b INT); +CREATE TABLE adt_ref (a INT REFERENCES adt_col(a)); +SELECT create_distributed_table('adt_table', 'a', colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('adt_col', 'a', colocate_with:='adt_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('adt_ref', 'a', colocate_with:='adt_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO adt_table VALUES (1, 2), (3, 4), (5, 6); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.adt_table_1503060 AS citus_table_alias (a, b) VALUES (1,2), (5,6) +INSERT INTO adt_col VALUES (3, 4), (5, 6), (7, 8); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.adt_col_1503064 AS citus_table_alias (a, b) VALUES (5,6) +INSERT INTO adt_ref VALUES (3), (5); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.adt_ref_1503068 AS citus_table_alias (a) VALUES (5) +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text LIKE 'adt%'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_col | distributed | a | 4 + adt_ref | distributed | a | 4 + adt_table | distributed | a | 4 +(3 rows) + +SELECT STRING_AGG(table_name::text, ', ' ORDER BY 1) AS "Colocation Groups" FROM public.citus_tables WHERE table_name::text LIKE 'adt%' GROUP BY colocation_id ORDER BY 1; + Colocation Groups +--------------------------------------------------------------------- + adt_col, adt_ref, adt_table +(1 row) + +SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint + WHERE (conrelid::regclass::text = 'adt_col' OR confrelid::regclass::text = 'adt_col') ORDER BY 1; + Referencing Table | Definition +--------------------------------------------------------------------- + adt_col | UNIQUE (a) + adt_ref | FOREIGN KEY (a) REFERENCES adt_col(a) +(2 rows) + +SET client_min_messages TO WARNING; +SELECT alter_distributed_table('adt_table', shard_count:=6, cascade_to_colocated:=true); + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEFAULT; +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text LIKE 'adt%'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_col | distributed | a | 6 + adt_ref | distributed | a | 6 + adt_table | distributed | a | 6 +(3 rows) + +SELECT STRING_AGG(table_name::text, ', ' ORDER BY 1) AS "Colocation Groups" FROM public.citus_tables WHERE table_name::text LIKE 'adt%' GROUP BY colocation_id ORDER BY 1; + Colocation Groups +--------------------------------------------------------------------- + adt_col, adt_ref, adt_table +(1 row) + +SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint + WHERE (conrelid::regclass::text = 'adt_col' OR confrelid::regclass::text = 'adt_col') ORDER BY 1; + Referencing Table | Definition +--------------------------------------------------------------------- + adt_col | UNIQUE (a) + adt_ref | FOREIGN KEY (a) REFERENCES adt_col(a) +(2 rows) + +SELECT alter_distributed_table('adt_table', distribution_column:='b', colocate_with:='none'); +NOTICE: creating a new table for coordinator_shouldhaveshards.adt_table +NOTICE: moving the data of coordinator_shouldhaveshards.adt_table +NOTICE: dropping the old coordinator_shouldhaveshards.adt_table +NOTICE: renaming the new table to coordinator_shouldhaveshards.adt_table + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text LIKE 'adt%'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_col | distributed | a | 6 + adt_ref | distributed | a | 6 + adt_table | distributed | b | 6 +(3 rows) + +SELECT STRING_AGG(table_name::text, ', ' ORDER BY 1) AS "Colocation Groups" FROM public.citus_tables WHERE table_name::text LIKE 'adt%' GROUP BY colocation_id ORDER BY 1; + Colocation Groups +--------------------------------------------------------------------- + adt_col, adt_ref + adt_table +(2 rows) + +SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint + WHERE (conrelid::regclass::text = 'adt_col' OR confrelid::regclass::text = 'adt_col') ORDER BY 1; + Referencing Table | Definition +--------------------------------------------------------------------- + adt_col | UNIQUE (a) + adt_ref | FOREIGN KEY (a) REFERENCES adt_col(a) +(2 rows) + +SELECT * FROM adt_table ORDER BY 1; + a | b +--------------------------------------------------------------------- + 1 | 2 + 3 | 4 + 5 | 6 +(3 rows) + +SELECT * FROM adt_col ORDER BY 1; + a | b +--------------------------------------------------------------------- + 3 | 4 + 5 | 6 + 7 | 8 +(3 rows) + +SELECT * FROM adt_ref ORDER BY 1; + a +--------------------------------------------------------------------- + 3 + 5 +(2 rows) + +SET client_min_messages TO WARNING; +BEGIN; +INSERT INTO adt_table SELECT x, x+1 FROM generate_series(1, 1000) x; +SELECT alter_distributed_table('adt_table', distribution_column:='a'); + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT COUNT(*) FROM adt_table; + count +--------------------------------------------------------------------- + 1003 +(1 row) + +END; +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text = 'adt_table'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_table | distributed | a | 6 +(1 row) + +SET client_min_messages TO DEFAULT; +-- issue 4508 table_1 and table_2 are used to test +-- some edge cases around intermediate result pruning +CREATE TABLE table_1 (key int, value text); +SELECT create_distributed_table('table_1', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE table_2 (key int, value text); +SELECT create_distributed_table('table_2', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO table_1 VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.table_1_1503102 AS citus_table_alias (key, value) VALUES (1,'1'::text) +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.table_1_1503105 AS citus_table_alias (key, value) VALUES (2,'2'::text) +INSERT INTO table_2 VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'), (6, '6'); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.table_2_1503106 AS citus_table_alias (key, value) VALUES (1,'1'::text), (5,'5'::text) +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.table_2_1503109 AS citus_table_alias (key, value) VALUES (2,'2'::text) +SET citus.log_intermediate_results TO ON; +SET client_min_messages to debug1; +WITH a AS (SELECT * FROM table_1 ORDER BY 1,2 DESC LIMIT 1) +SELECT count(*), +key +FROM a JOIN table_2 USING (key) +GROUP BY key +HAVING (max(table_2.value) >= (SELECT value FROM a)); +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: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, a.key 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)) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +NOTICE: executing the command locally: SELECT key, value FROM coordinator_shouldhaveshards.table_1_1503102 table_1 WHERE true ORDER BY key, value DESC LIMIT '1'::bigint +NOTICE: executing the command locally: SELECT key, value FROM coordinator_shouldhaveshards.table_1_1503105 table_1 WHERE true ORDER BY key, value DESC LIMIT '1'::bigint +NOTICE: executing the command locally: SELECT count(*) AS count, worker_column_1 AS key, max(worker_column_2) AS worker_column_3 FROM (SELECT a.key AS worker_column_1, table_2.value AS worker_column_2 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_1503106 table_2(key, value) USING (key))) worker_subquery GROUP BY worker_column_1 +NOTICE: executing the command locally: SELECT count(*) AS count, worker_column_1 AS key, max(worker_column_2) AS worker_column_3 FROM (SELECT a.key AS worker_column_1, table_2.value AS worker_column_2 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_1503109 table_2(key, value) USING (key))) worker_subquery GROUP BY worker_column_1 + count | key +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +WITH a AS (SELECT * FROM table_1 ORDER BY 1,2 DESC LIMIT 1) +INSERT INTO table_1 SELECT count(*), +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: 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)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT auto_coerced_by_citus_0 AS key, auto_coerced_by_citus_1 AS value FROM (SELECT intermediate_result.auto_coerced_by_citus_0, intermediate_result.auto_coerced_by_citus_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(auto_coerced_by_citus_0 integer, auto_coerced_by_citus_1 text)) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +NOTICE: executing the command locally: SELECT key, value FROM coordinator_shouldhaveshards.table_1_1503102 table_1 WHERE true ORDER BY key, value DESC LIMIT '1'::bigint +NOTICE: executing the command locally: SELECT key, value FROM coordinator_shouldhaveshards.table_1_1503105 table_1 WHERE true ORDER BY key, value DESC LIMIT '1'::bigint +DEBUG: Subplan XXX_2 will be written to local file +NOTICE: executing the command locally: SELECT count(*) AS auto_coerced_by_citus_0, (worker_column_1)::text AS auto_coerced_by_citus_1, worker_column_1 AS discarded_target_item_1, max(worker_column_2) AS worker_column_4 FROM (SELECT a.key AS worker_column_1, table_2.value AS worker_column_2 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_1503106 table_2(key, value) USING (key))) worker_subquery GROUP BY worker_column_1 +NOTICE: executing the command locally: SELECT count(*) AS auto_coerced_by_citus_0, (worker_column_1)::text AS auto_coerced_by_citus_1, worker_column_1 AS discarded_target_item_1, max(worker_column_2) AS worker_column_4 FROM (SELECT a.key AS worker_column_1, table_2.value AS worker_column_2 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_1503109 table_2(key, value) USING (key))) worker_subquery GROUP BY worker_column_1 +NOTICE: executing the command locally: SELECT auto_coerced_by_citus_0 AS key, auto_coerced_by_citus_1 AS value FROM (SELECT intermediate_result.auto_coerced_by_citus_0, intermediate_result.auto_coerced_by_citus_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(auto_coerced_by_citus_0 integer, auto_coerced_by_citus_1 text)) citus_insert_select_subquery +NOTICE: executing the copy locally for shard xxxxx +WITH stats AS ( + SELECT count(key) m FROM table_1 +), +inserts AS ( + INSERT INTO table_2 + SELECT key, count(*) + FROM table_1 + WHERE key >= (SELECT m FROM stats) + GROUP BY key + HAVING count(*) <= (SELECT m FROM stats) + LIMIT 1 + RETURNING * +) 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: 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 +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +NOTICE: executing the command locally: SELECT count(key) AS m FROM coordinator_shouldhaveshards.table_1_1503102 table_1 WHERE true +NOTICE: executing the command locally: SELECT count(key) AS m FROM coordinator_shouldhaveshards.table_1_1503105 table_1 WHERE true +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Collecting INSERT ... SELECT results on coordinator +NOTICE: executing the command locally: SELECT worker_column_1 AS key, (count(*))::text AS value FROM (SELECT table_1.key AS worker_column_1 FROM coordinator_shouldhaveshards.table_1_1503102 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))) worker_subquery GROUP BY worker_column_1 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'::bigint +NOTICE: executing the command locally: SELECT worker_column_1 AS key, (count(*))::text AS value FROM (SELECT table_1.key AS worker_column_1 FROM coordinator_shouldhaveshards.table_1_1503105 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))) worker_subquery GROUP BY worker_column_1 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'::bigint +NOTICE: executing the command locally: 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 + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- a helper function which return true if the coordinated +-- trannsaction uses 2PC +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE FUNCTION coordinated_transaction_should_use_2PC() +RETURNS BOOL LANGUAGE C STRICT VOLATILE AS 'citus', +$$coordinated_transaction_should_use_2PC$$; +RESET citus.enable_metadata_sync; +-- a local SELECT followed by remote SELECTs +-- does not trigger 2PC +BEGIN; + SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- +(0 rows) + + WITH cte_1 AS (SELECT y FROM test WHERE x = 1 LIMIT 5) SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + + SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + + WITH cte_1 as (SELECT * FROM test LIMIT 5) SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +-- remote SELECTs followed by local SELECTs +-- does not trigger 2PC +BEGIN; + SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + + WITH cte_1 as (SELECT * FROM test LIMIT 5) SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + + SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- +(0 rows) + + WITH cte_1 AS (SELECT y FROM test WHERE x = 1 LIMIT 5) SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +-- a local SELECT followed by a remote Modify +-- triggers 2PC +BEGIN; + SELECT y FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT y FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + y +--------------------------------------------------------------------- +(0 rows) + + UPDATE test SET y = y +1; +NOTICE: executing the command locally: UPDATE coordinator_shouldhaveshards.test_1503000 test SET y = (y OPERATOR(pg_catalog.+) 1) +NOTICE: executing the command locally: UPDATE coordinator_shouldhaveshards.test_1503003 test SET y = (y OPERATOR(pg_catalog.+) 1) + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +-- a local modify followed by a remote SELECT +-- triggers 2PC +BEGIN; + INSERT INTO test VALUES (1,1); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.test_1503000 (x, y) VALUES (1, 1) + SELECT count(*) FROM test; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503003 test WHERE true + count +--------------------------------------------------------------------- + 1 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +-- a local modify followed by a remote MODIFY +-- triggers 2PC +BEGIN; + INSERT INTO test VALUES (1,1); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.test_1503000 (x, y) VALUES (1, 1) + UPDATE test SET y = y +1; +NOTICE: executing the command locally: UPDATE coordinator_shouldhaveshards.test_1503000 test SET y = (y OPERATOR(pg_catalog.+) 1) +NOTICE: executing the command locally: UPDATE coordinator_shouldhaveshards.test_1503003 test SET y = (y OPERATOR(pg_catalog.+) 1) + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +-- a local modify followed by a remote single shard MODIFY +-- triggers 2PC +BEGIN; + INSERT INTO test VALUES (1,1); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.test_1503000 (x, y) VALUES (1, 1) + INSERT INTO test VALUES (3,3); + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +-- a remote single shard modify followed by a local single +-- shard MODIFY triggers 2PC +BEGIN; + INSERT INTO test VALUES (3,3); + INSERT INTO test VALUES (1,1); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.test_1503000 (x, y) VALUES (1, 1) + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +-- a remote single shard select followed by a local single +-- shard MODIFY triggers 2PC. But, the transaction manager +-- is smart enough to skip sending 2PC as the remote +-- command is read only +BEGIN; + SELECT count(*) FROM test WHERE x = 3; + count +--------------------------------------------------------------------- + 2 +(1 row) + + INSERT INTO test VALUES (1,1); +NOTICE: executing the command locally: INSERT INTO coordinator_shouldhaveshards.test_1503000 (x, y) VALUES (1, 1) + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + + SET LOCAL citus.log_remote_commands TO ON; +COMMIT; +NOTICE: issuing COMMIT +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +-- a local single shard select followed by a remote single +-- shard modify does not trigger 2PC +BEGIN; + SELECT count(*) FROM test WHERE x = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM coordinator_shouldhaveshards.test_1503000 test WHERE (x OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 5 +(1 row) + + INSERT INTO test VALUES (3,3); + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + + SET LOCAL citus.log_remote_commands TO ON; +COMMIT; +NOTICE: issuing COMMIT +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +RESET client_min_messages; +\set VERBOSITY terse +DROP TABLE ref_table; +NOTICE: executing the command locally: DROP TABLE IF EXISTS coordinator_shouldhaveshards.ref_table_xxxxx CASCADE +DELETE FROM test; +DROP TABLE test; +DROP TABLE dist_table; +DROP TABLE ref; +NOTICE: executing the command locally: DROP TABLE IF EXISTS coordinator_shouldhaveshards.ref_xxxxx CASCADE +DROP TABLE test_append_table; +DROP SCHEMA coordinator_shouldhaveshards CASCADE; +NOTICE: drop cascades to 20 other objects +SELECT 1 FROM master_set_node_property('localhost', :master_port, 'shouldhaveshards', false); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + diff --git a/src/test/regress/expected/cpu_priority.out b/src/test/regress/expected/cpu_priority.out index 5a397ef1c..008eacafd 100644 --- a/src/test/regress/expected/cpu_priority.out +++ b/src/test/regress/expected/cpu_priority.out @@ -85,14 +85,17 @@ 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 CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx master_move_shard_placement --------------------------------------------------------------------- @@ -101,13 +104,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 CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx master_move_shard_placement --------------------------------------------------------------------- @@ -116,13 +119,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 CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx master_move_shard_placement --------------------------------------------------------------------- @@ -138,21 +141,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 CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_split_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_split_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_split_slot_xxxxxxx_xxxxxxx) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx citus_split_shard_by_split_points --------------------------------------------------------------------- diff --git a/src/test/regress/expected/create_distributed_table_concurrently.out b/src/test/regress/expected/create_distributed_table_concurrently.out new file mode 100644 index 000000000..19298a39d --- /dev/null +++ b/src/test/regress/expected/create_distributed_table_concurrently.out @@ -0,0 +1,288 @@ +create schema create_distributed_table_concurrently; +set search_path to create_distributed_table_concurrently; +set citus.shard_replication_factor to 1; +-- make sure we have the coordinator in the metadata +SELECT 1 FROM citus_set_coordinator_host('localhost', :master_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +create table ref (id int primary key); +select create_reference_table('ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +insert into ref select s from generate_series(0,9) s; +create table test (key text, id int references ref (id) on delete cascade, t timestamptz default now()) partition by range (t); +create table test_1 partition of test for values from ('2022-01-01') to ('2022-12-31'); +create table test_2 partition of test for values from ('2023-01-01') to ('2023-12-31'); +insert into test (key,id,t) select s,s%10, '2022-01-01'::date + interval '1 year' * (s%2) from generate_series(1,100) s; +create table nocolo (x int, y int); +-- test error conditions +select create_distributed_table_concurrently('test','key', 'append'); +ERROR: only hash-distributed tables can be distributed without blocking writes +select create_distributed_table_concurrently('test','key', 'range'); +ERROR: only hash-distributed tables can be distributed without blocking writes +select create_distributed_table_concurrently('test','noexists', 'hash'); +ERROR: column "noexists" of relation "test" does not exist +select create_distributed_table_concurrently(0,'key'); +ERROR: relation with OID XXXX does not exist +select create_distributed_table_concurrently('ref','id'); +ERROR: table "ref" is already distributed +set citus.shard_replication_factor to 2; +select create_distributed_table_concurrently('test','key', 'hash'); +ERROR: cannot distribute a table concurrently when citus.shard_replication_factor > 1 +set citus.shard_replication_factor to 1; +begin; +select create_distributed_table_concurrently('test','key'); +ERROR: create_distributed_table_concurrently cannot run inside a transaction block +rollback; +select create_distributed_table_concurrently('test','key'), create_distributed_table_concurrently('test','key'); +NOTICE: relation test does not have a REPLICA IDENTITY or PRIMARY KEY +DETAIL: UPDATE and DELETE commands on the relation will error out during create_distributed_table_concurrently unless there is a REPLICA IDENTITY or PRIMARY KEY. INSERT commands will still work. +ERROR: multiple shard movements/splits via logical replication in the same transaction is currently not supported +select create_distributed_table_concurrently('nocolo','x'); +NOTICE: relation nocolo does not have a REPLICA IDENTITY or PRIMARY KEY +DETAIL: UPDATE and DELETE commands on the relation will error out during create_distributed_table_concurrently unless there is a REPLICA IDENTITY or PRIMARY KEY. INSERT commands will still work. + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +select create_distributed_table_concurrently('test','key', colocate_with := 'nocolo'); +ERROR: cannot colocate tables nocolo and test +DETAIL: Distribution column types don't match for nocolo and test. +select create_distributed_table_concurrently('test','key', colocate_with := 'noexists'); +ERROR: relation "noexists" does not exist +-- use colocate_with "default" +select create_distributed_table_concurrently('test','key', shard_count := 11); +NOTICE: relation test does not have a REPLICA IDENTITY or PRIMARY KEY +DETAIL: UPDATE and DELETE commands on the relation will error out during create_distributed_table_concurrently unless there is a REPLICA IDENTITY or PRIMARY KEY. INSERT commands will still work. + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +select shardcount from pg_dist_partition p join pg_dist_colocation c using (colocationid) where logicalrelid = 'test'::regclass; + shardcount +--------------------------------------------------------------------- + 11 +(1 row) + +select count(*) from pg_dist_shard where logicalrelid = 'test'::regclass; + count +--------------------------------------------------------------------- + 11 +(1 row) + +-- verify queries still work +select count(*) from test; + count +--------------------------------------------------------------------- + 100 +(1 row) + +select key, id from test where key = '1'; + key | id +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +select count(*) from test_1; + count +--------------------------------------------------------------------- + 50 +(1 row) + +-- verify that the foreign key to reference table was created +begin; +delete from ref; +select count(*) from test; + count +--------------------------------------------------------------------- + 0 +(1 row) + +rollback; +-- verify that we can undistribute the table +begin; +select undistribute_table('test', cascade_via_foreign_keys := true); +NOTICE: converting the partitions of create_distributed_table_concurrently.test +NOTICE: creating a new table for create_distributed_table_concurrently.test +NOTICE: dropping the old create_distributed_table_concurrently.test +NOTICE: renaming the new table to create_distributed_table_concurrently.test +NOTICE: creating a new table for create_distributed_table_concurrently.ref +NOTICE: moving the data of create_distributed_table_concurrently.ref +NOTICE: dropping the old create_distributed_table_concurrently.ref +NOTICE: renaming the new table to create_distributed_table_concurrently.ref +NOTICE: creating a new table for create_distributed_table_concurrently.test_1 +NOTICE: moving the data of create_distributed_table_concurrently.test_1 +NOTICE: dropping the old create_distributed_table_concurrently.test_1 +NOTICE: renaming the new table to create_distributed_table_concurrently.test_1 +NOTICE: creating a new table for create_distributed_table_concurrently.test_2 +NOTICE: moving the data of create_distributed_table_concurrently.test_2 +NOTICE: dropping the old create_distributed_table_concurrently.test_2 +NOTICE: renaming the new table to create_distributed_table_concurrently.test_2 + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +rollback; +-- verify that we can co-locate with create_distributed_table_concurrently +create table test2 (x text primary key, y text); +insert into test2 (x,y) select s,s from generate_series(1,100) s; +select create_distributed_table_concurrently('test2','x', colocate_with := 'test'); + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +-- verify co-located joins work +select count(*) from test join test2 on (key = x); + count +--------------------------------------------------------------------- + 100 +(1 row) + +select id, y from test join test2 on (key = x) where key = '1'; + id | y +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +-- verify co-locaed foreign keys work +alter table test add constraint fk foreign key (key) references test2 (x); +-------foreign key tests among different table types-------- +-- verify we do not allow foreign keys from reference table to distributed table concurrently +create table ref_table1(id int); +create table dist_table1(id int primary key); +select create_reference_table('ref_table1'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +alter table ref_table1 add constraint fkey foreign key (id) references dist_table1(id); +select create_distributed_table_concurrently('dist_table1', 'id'); +ERROR: relation dist_table1 is referenced by a foreign key from ref_table1 +DETAIL: foreign keys from a reference table to a distributed table are not supported. +-- verify we do not allow foreign keys from citus local table to distributed table concurrently +create table citus_local_table1(id int); +select citus_add_local_table_to_metadata('citus_local_table1'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +create table dist_table2(id int primary key); +alter table citus_local_table1 add constraint fkey foreign key (id) references dist_table2(id); +select create_distributed_table_concurrently('dist_table2', 'id'); +ERROR: relation dist_table2 is referenced by a foreign key from citus_local_table1 +DETAIL: foreign keys from a citus local table to a distributed table are not supported. +-- verify we do not allow foreign keys from regular table to distributed table concurrently +create table local_table1(id int); +create table dist_table3(id int primary key); +alter table local_table1 add constraint fkey foreign key (id) references dist_table3(id); +select create_distributed_table_concurrently('dist_table3', 'id'); +ERROR: relation dist_table3 is referenced by a foreign key from local_table1 +DETAIL: foreign keys from a regular table to a distributed table are not supported. +-- verify we allow foreign keys from distributed table to reference table concurrently +create table ref_table2(id int primary key); +select create_reference_table('ref_table2'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +create table dist_table4(id int references ref_table2(id)); +select create_distributed_table_concurrently('dist_table4', 'id'); +NOTICE: relation dist_table4 does not have a REPLICA IDENTITY or PRIMARY KEY +DETAIL: UPDATE and DELETE commands on the relation will error out during create_distributed_table_concurrently unless there is a REPLICA IDENTITY or PRIMARY KEY. INSERT commands will still work. + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +insert into ref_table2 select s from generate_series(1,100) s; +insert into dist_table4 select s from generate_series(1,100) s; +select count(*) as total from dist_table4; + total +--------------------------------------------------------------------- + 100 +(1 row) + +-- verify we do not allow foreign keys from distributed table to citus local table concurrently +create table citus_local_table2(id int primary key); +select citus_add_local_table_to_metadata('citus_local_table2'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +create table dist_table5(id int references citus_local_table2(id)); +select create_distributed_table_concurrently('dist_table5', 'id'); +NOTICE: relation dist_table5 does not have a REPLICA IDENTITY or PRIMARY KEY +DETAIL: UPDATE and DELETE commands on the relation will error out during create_distributed_table_concurrently unless there is a REPLICA IDENTITY or PRIMARY KEY. INSERT commands will still work. +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 +-- verify we do not allow foreign keys from distributed table to regular table concurrently +create table local_table2(id int primary key); +create table dist_table6(id int references local_table2(id)); +select create_distributed_table_concurrently('dist_table6', 'id'); +ERROR: relation local_table2 is referenced by a foreign key from dist_table6 +DETAIL: foreign keys from a distributed table to a regular table are not supported. +-------foreign key tests among different table types-------- +-- columnar tests -- +-- create table with partitions +create table test_columnar (id int) partition by range (id); +create table test_columnar_1 partition of test_columnar for values from (1) to (51); +create table test_columnar_2 partition of test_columnar for values from (51) to (101) using columnar; +-- load some data +insert into test_columnar (id) select s from generate_series(1,100) s; +-- distribute table +select create_distributed_table_concurrently('test_columnar','id'); +NOTICE: relation test_columnar does not have a REPLICA IDENTITY or PRIMARY KEY +DETAIL: UPDATE and DELETE commands on the relation will error out during create_distributed_table_concurrently unless there is a REPLICA IDENTITY or PRIMARY KEY. INSERT commands will still work. + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +-- verify queries still work +select count(*) from test_columnar; + count +--------------------------------------------------------------------- + 100 +(1 row) + +select id from test_columnar where id = 1; + id +--------------------------------------------------------------------- + 1 +(1 row) + +select id from test_columnar where id = 51; + id +--------------------------------------------------------------------- + 51 +(1 row) + +select count(*) from test_columnar_1; + count +--------------------------------------------------------------------- + 50 +(1 row) + +select count(*) from test_columnar_2; + count +--------------------------------------------------------------------- + 50 +(1 row) + +-- columnar tests -- +set client_min_messages to warning; +drop schema create_distributed_table_concurrently cascade; diff --git a/src/test/regress/expected/cte_inline.out b/src/test/regress/expected/cte_inline.out index 072f076d6..1e1f410c3 100644 --- a/src/test/regress/expected/cte_inline.out +++ b/src/test/regress/expected/cte_inline.out @@ -1,3 +1,17 @@ +-- +-- CTE_INLINE +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA cte_inline; SET search_path TO cte_inline; SET citus.next_shard_id TO 1960000; @@ -423,6 +437,8 @@ DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] (1 row) -- EXPLAIN should show the differences between MATERIALIZED and NOT MATERIALIZED +\set VERBOSITY terse +SELECT public.coordinator_plan_with_subplans($Q$ EXPLAIN (COSTS OFF) WITH cte_1 AS (SELECT * FROM test_table) SELECT count(*) @@ -431,35 +447,44 @@ FROM JOIN cte_1 as second_entry USING (key); +$Q$); DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT key, value, other_value FROM cte_inline.test_table DEBUG: Router planner cannot handle multi-shard select queries DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) first_entry JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) second_entry USING (key)) DEBUG: Creating router plan - QUERY PLAN + coordinator_plan_with_subplans --------------------------------------------------------------------- Custom Scan (Citus Adaptive) -> Distributed Subplan XXX_1 -> Custom Scan (Citus Adaptive) Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Seq Scan on test_table_1960000 test_table Task Count: 1 - Tasks Shown: All - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Aggregate - -> Merge Join - Merge Cond: (intermediate_result.key = intermediate_result_1.key) - -> Sort - Sort Key: intermediate_result.key - -> Function Scan on read_intermediate_result intermediate_result - -> Sort - Sort Key: intermediate_result_1.key - -> Function Scan on read_intermediate_result intermediate_result_1 -(21 rows) +(5 rows) + +\set VERBOSITY default +-- enable_group_by_reordering is a new GUC introduced in PG15 +-- it does some optimization of the order of group by keys which results +-- in a different explain output plan between PG13/14 and PG15 +-- Hence we set that GUC to off. +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +SET enable_group_by_reordering TO off; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM SET enable_group_by_reordering TO off;$$); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,t) + (localhost,57638,t,t) +(2 rows) EXPLAIN (COSTS OFF) WITH cte_1 AS NOT MATERIALIZED (SELECT * FROM test_table) SELECT @@ -499,6 +524,22 @@ DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] -> Seq Scan on test_table_1960000 test_table_1 (12 rows) +\if :server_version_ge_15 +RESET enable_group_by_reordering; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM RESET enable_group_by_reordering;$$); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,t) + (localhost,57638,t,t) +(2 rows) + -- ctes with volatile functions are not -- inlined WITH cte_1 AS (SELECT *, random() FROM test_table) @@ -830,10 +871,10 @@ DEBUG: CTE fist_table_cte is going to be inlined via distributed planning DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 0 with name 'key' -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960000 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960001 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960002 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960003 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960000 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960001 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960002 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960003 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) -- the following INSERT..SELECT is even more interesting -- the CTE becomes pushdownable INSERT INTO test_table @@ -844,10 +885,10 @@ WITH fist_table_cte AS FROM fist_table_cte; DEBUG: CTE fist_table_cte is going to be inlined via distributed planning -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960000 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960000 test_table) fist_table_cte WHERE (key IS NOT NULL) -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960001 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960001 test_table) fist_table_cte WHERE (key IS NOT NULL) -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960002 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960002 test_table) fist_table_cte WHERE (key IS NOT NULL) -DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960003 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960003 test_table) fist_table_cte WHERE (key IS NOT NULL) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960000 AS citus_table_alias (key, value) SELECT fist_table_cte.key, fist_table_cte.value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960000 test_table) fist_table_cte WHERE (fist_table_cte.key IS NOT NULL) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960001 AS citus_table_alias (key, value) SELECT fist_table_cte.key, fist_table_cte.value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960001 test_table) fist_table_cte WHERE (fist_table_cte.key IS NOT NULL) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960002 AS citus_table_alias (key, value) SELECT fist_table_cte.key, fist_table_cte.value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960002 test_table) fist_table_cte WHERE (fist_table_cte.key IS NOT NULL) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960003 AS citus_table_alias (key, value) SELECT fist_table_cte.key, fist_table_cte.value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960003 test_table) fist_table_cte WHERE (fist_table_cte.key IS NOT NULL) -- update/delete/modifying ctes -- we don't support any cte inlining in modifications -- queries and modifying CTEs diff --git a/src/test/regress/expected/cte_inline_0.out b/src/test/regress/expected/cte_inline_0.out new file mode 100644 index 000000000..ebaa172bb --- /dev/null +++ b/src/test/regress/expected/cte_inline_0.out @@ -0,0 +1,1541 @@ +-- +-- CTE_INLINE +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA cte_inline; +SET search_path TO cte_inline; +SET citus.next_shard_id TO 1960000; +CREATE TABLE test_table (key int, value text, other_value jsonb); +SELECT create_distributed_table ('test_table', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO test_table SELECT i % 10, 'test' || i, row_to_json(row(i, i*18, 'test' || i)) FROM generate_series (0, 100) i; +SET client_min_messages TO DEBUG; +-- Citus should not inline this CTE because otherwise it cannot +-- plan the query +WITH cte_1 AS (SELECT * FROM test_table) +SELECT + * +FROM + test_table LEFT JOIN cte_1 USING (value) +ORDER BY 1 DESC LIMIT 3; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test99 | 9 | {"f1": 99, "f2": 1782, "f3": "test99"} | 9 | {"f1": 99, "f2": 1782, "f3": "test99"} + test98 | 8 | {"f1": 98, "f2": 1764, "f3": "test98"} | 8 | {"f1": 98, "f2": 1764, "f3": "test98"} + test97 | 7 | {"f1": 97, "f2": 1746, "f3": "test97"} | 7 | {"f1": 97, "f2": 1746, "f3": "test97"} +(3 rows) + +-- Should still not be inlined even if NOT MATERIALIZED is passed +WITH cte_1 AS NOT MATERIALIZED (SELECT * FROM test_table) +SELECT + * +FROM + test_table LEFT JOIN cte_1 USING (value) +ORDER BY 2 DESC LIMIT 1; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.key DESC LIMIT 1 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 1 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test9 | 9 | {"f1": 9, "f2": 162, "f3": "test9"} | 9 | {"f1": 9, "f2": 162, "f3": "test9"} +(1 row) + +-- the cte can be inlined because the unsupported +-- part of the query (subquery in WHERE clause) +-- doesn't access the cte +WITH cte_1 AS (SELECT * FROM test_table) +SELECT + count(*) +FROM + cte_1 +WHERE + key IN ( + SELECT + (SELECT 1) + FROM + test_table WHERE key = 1 + ); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +DEBUG: generating subplan XXX_1 for subquery SELECT (SELECT 1) FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.=) 1) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1 WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result."?column?" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer))) +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- a similar query as the above, and this time the planning +-- fails, but it fails because the subquery in WHERE clause +-- cannot be planned by Citus +WITH cte_1 AS (SELECT * FROM test_table) +SELECT + count(*) +FROM + cte_1 +WHERE + key IN ( + SELECT + key + FROM + test_table + FOR UPDATE + ); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: SELECT FOR UPDATE with table replication factor > 1 not supported for non-reference tables. +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: SELECT FOR UPDATE with table replication factor > 1 not supported for non-reference tables. +ERROR: could not run distributed query with FOR UPDATE/SHARE commands +HINT: Consider using an equality filter on the distributed table's partition column. +-- Citus does the inlining, the planning fails +-- and retries without inlining, which works +-- fine later via recursive planning +WITH cte_1 AS + (SELECT * + FROM test_table) +SELECT *, (SELECT 1) +FROM + (SELECT * + FROM cte_1) AS foo +ORDER BY 2 DESC LIMIT 1; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 1 + key | value | other_value | ?column? +--------------------------------------------------------------------- + 9 | test99 | {"f1": 99, "f2": 1782, "f3": "test99"} | 1 +(1 row) + +-- a little more complicated query tree +-- Citus does the inlining, the planning fails +-- and retries without inlining, which works +WITH top_cte AS + (SELECT * + FROM test_table) +SELECT count(*) +FROM top_cte, + (WITH cte_1 AS + (SELECT * + FROM test_table) SELECT *, (SELECT 1) + FROM + (SELECT * + FROM cte_1) AS foo) AS bar; +DEBUG: CTE top_cte is going to be inlined via distributed planning +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value, (SELECT 1) FROM (SELECT cte_1.key, cte_1.value, cte_1.other_value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1) foo +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) top_cte, (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value, intermediate_result."?column?" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb, "?column?" integer)) bar(key, value, other_value, "?column?") +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 10201 +(1 row) + +-- CTE is used inside a subquery in WHERE clause +-- the query wouldn't work by inlining, so Citus +-- retries again via recursive planning, which +-- works fine +WITH cte_1 AS + (SELECT * + FROM test_table) +SELECT count(*) +FROM test_table +WHERE KEY IN + (SELECT (SELECT 1) + FROM + (SELECT *, + random() + FROM + (SELECT * + FROM cte_1) AS foo) AS bar); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 (SELECT 1) FROM (SELECT foo.key, foo.value, foo.other_value, random() AS random FROM (SELECT cte_1.key, cte_1.value, cte_1.other_value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1) foo) bar +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result."?column?" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer))) +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- cte_1 is used inside another CTE, but still +-- doesn't work when inlined because it is finally +-- used in an unsupported query +-- but still works fine because recursive planning +-- kicks in +WITH cte_1 AS + (SELECT * + FROM test_table) +SELECT (SELECT 1) AS KEY FROM ( + WITH cte_2 AS (SELECT *, random() + FROM (SELECT *,random() FROM cte_1) as foo) +SELECT *, random() FROM cte_2) as bar ORDER BY 1 DESC LIMIT 3; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_2: SELECT key, value, other_value, random, random() AS random FROM (SELECT cte_1.key, cte_1.value, cte_1.other_value, random() AS random FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1) foo +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT (SELECT 1) AS key FROM (SELECT cte_2.key, cte_2.value, cte_2.other_value, cte_2.random, cte_2.random_1 AS random, random() AS random FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value, intermediate_result.random, intermediate_result.random_1 AS random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb, random double precision, random_1 double precision)) cte_2(key, value, other_value, random, random_1)) bar(key, value, other_value, random, random_1, random_2) ORDER BY (SELECT 1) DESC LIMIT 3 +DEBUG: Creating router plan + key +--------------------------------------------------------------------- + 1 + 1 + 1 +(3 rows) + +-- in this example, cte_2 can be inlined, because it is not used +-- on any query that Citus cannot plan. However, cte_1 should not be +-- inlined, because it is used with a subquery in target list +WITH cte_1 AS (SELECT * FROM test_table), + cte_2 AS (select * from test_table) +SELECT + count(*) +FROM + (SELECT *, (SELECT 1) FROM cte_1) as foo + JOIN + cte_2 + ON (true); +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: 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 key, value, other_value FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT cte_1.key, cte_1.value, cte_1.other_value, (SELECT 1) FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1) foo(key, value, other_value, "?column?") JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_2 ON (true)) +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 10201 +(1 row) + +-- unreferenced CTEs are just ignored +-- by Citus/Postgres +WITH a AS (SELECT * FROM test_table) +SELECT + *, row_number() OVER () +FROM + test_table +WHERE + key = 1 +ORDER BY 3 DESC +LIMIT 5; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 + key | value | other_value | row_number +--------------------------------------------------------------------- + 1 | test91 | {"f1": 91, "f2": 1638, "f3": "test91"} | 10 + 1 | test81 | {"f1": 81, "f2": 1458, "f3": "test81"} | 9 + 1 | test71 | {"f1": 71, "f2": 1278, "f3": "test71"} | 8 + 1 | test61 | {"f1": 61, "f2": 1098, "f3": "test61"} | 7 + 1 | test51 | {"f1": 51, "f2": 918, "f3": "test51"} | 6 +(5 rows) + +-- router queries are affected by the distributed +-- cte inlining +WITH a AS (SELECT * FROM test_table WHERE key = 1) +SELECT + *, (SELECT 1) +FROM + a +WHERE + key = 1 +ORDER BY 1 DESC +LIMIT 5; +DEBUG: CTE a is going to be inlined via distributed planning +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 + key | value | other_value | ?column? +--------------------------------------------------------------------- + 1 | test1 | {"f1": 1, "f2": 18, "f3": "test1"} | 1 + 1 | test11 | {"f1": 11, "f2": 198, "f3": "test11"} | 1 + 1 | test21 | {"f1": 21, "f2": 378, "f3": "test21"} | 1 + 1 | test31 | {"f1": 31, "f2": 558, "f3": "test31"} | 1 + 1 | test41 | {"f1": 41, "f2": 738, "f3": "test41"} | 1 +(5 rows) + +-- non router queries are affected by the distributed +-- cte inlining as well +WITH a AS (SELECT * FROM test_table) +SELECT + count(*) +FROM + a +WHERE + key = 1; +DEBUG: CTE a is going to be inlined via distributed planning +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- explicitely using NOT MATERIALIZED should result in the same +WITH a AS NOT MATERIALIZED (SELECT * FROM test_table) +SELECT + count(*) +FROM + a +WHERE + key = 1; +DEBUG: CTE a is going to be inlined via distributed planning +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- using MATERIALIZED should cause inlining not to happen +WITH a AS MATERIALIZED (SELECT * FROM test_table) +SELECT + count(*) +FROM + a +WHERE + key = 1; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) a WHERE (key OPERATOR(pg_catalog.=) 1) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- EXPLAIN should show the difference between materialized an not materialized +EXPLAIN (COSTS OFF) WITH a AS (SELECT * FROM test_table) +SELECT + count(*) +FROM + a +WHERE + key = 1; +DEBUG: CTE a is going to be inlined via distributed planning +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Aggregate + -> Seq Scan on test_table_1960000 test_table + Filter: (key = 1) +(8 rows) + +EXPLAIN (COSTS OFF) WITH a AS MATERIALIZED (SELECT * FROM test_table) +SELECT + count(*) +FROM + a +WHERE + key = 1; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) a WHERE (key OPERATOR(pg_catalog.=) 1) +DEBUG: Creating router plan + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on test_table_1960000 test_table + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Aggregate + -> Function Scan on read_intermediate_result intermediate_result + Filter: (key = 1) +(15 rows) + +-- citus should not inline the CTE because it is used multiple times +WITH cte_1 AS (SELECT * FROM test_table) +SELECT + count(*) +FROM + cte_1 as first_entry + JOIN + cte_1 as second_entry + USING (key); +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) first_entry JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) second_entry USING (key)) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 1021 +(1 row) + +-- NOT MATERIALIZED should cause the query to be inlined twice +WITH cte_1 AS NOT MATERIALIZED (SELECT * FROM test_table) +SELECT + count(*) +FROM + cte_1 as first_entry + JOIN + cte_1 as second_entry + USING (key); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [0,1073741823] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [1073741824,2147483647] +DEBUG: join prunable for intervals [-1073741824,-1] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [-1073741824,-1] and [0,1073741823] +DEBUG: join prunable for intervals [-1073741824,-1] and [1073741824,2147483647] +DEBUG: join prunable for intervals [0,1073741823] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [0,1073741823] and [-1073741824,-1] +DEBUG: join prunable for intervals [0,1073741823] and [1073741824,2147483647] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-1073741824,-1] +DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] + count +--------------------------------------------------------------------- + 1021 +(1 row) + +-- EXPLAIN should show the differences between MATERIALIZED and NOT MATERIALIZED +\set VERBOSITY terse +SELECT public.coordinator_plan_with_subplans($Q$ +EXPLAIN (COSTS OFF) WITH cte_1 AS (SELECT * FROM test_table) +SELECT + count(*) +FROM + cte_1 as first_entry + JOIN + cte_1 as second_entry + USING (key); +$Q$); +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) first_entry JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) second_entry USING (key)) +DEBUG: Creating router plan + coordinator_plan_with_subplans +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Task Count: 1 +(5 rows) + +\set VERBOSITY default +-- enable_group_by_reordering is a new GUC introduced in PG15 +-- it does some optimization of the order of group by keys which results +-- in a different explain output plan between PG13/14 and PG15 +-- Hence we set that GUC to off. +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +SET enable_group_by_reordering TO off; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM SET enable_group_by_reordering TO off;$$); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,t) + (localhost,57638,t,t) +(2 rows) + +EXPLAIN (COSTS OFF) WITH cte_1 AS NOT MATERIALIZED (SELECT * FROM test_table) +SELECT + count(*) +FROM + cte_1 as first_entry + JOIN + cte_1 as second_entry + USING (key); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [0,1073741823] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [1073741824,2147483647] +DEBUG: join prunable for intervals [-1073741824,-1] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [-1073741824,-1] and [0,1073741823] +DEBUG: join prunable for intervals [-1073741824,-1] and [1073741824,2147483647] +DEBUG: join prunable for intervals [0,1073741823] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [0,1073741823] and [-1073741824,-1] +DEBUG: join prunable for intervals [0,1073741823] and [1073741824,2147483647] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-1073741824,-1] +DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] + QUERY PLAN +--------------------------------------------------------------------- + Aggregate + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Aggregate + -> Hash Join + Hash Cond: (test_table.key = test_table_1.key) + -> Seq Scan on test_table_1960000 test_table + -> Hash + -> Seq Scan on test_table_1960000 test_table_1 +(12 rows) + +\if :server_version_ge_15 +RESET enable_group_by_reordering; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM RESET enable_group_by_reordering;$$); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,t) + (localhost,57638,t,t) +(2 rows) + +-- ctes with volatile functions are not +-- inlined +WITH cte_1 AS (SELECT *, random() FROM test_table) +SELECT + key, value +FROM + cte_1 +ORDER BY 2 DESC LIMIT 1; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT key, value, other_value, random() AS random FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, value FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb, random double precision)) cte_1 ORDER BY value DESC LIMIT 1 +DEBUG: Creating router plan + key | value +--------------------------------------------------------------------- + 9 | test99 +(1 row) + +-- even with NOT MATERIALIZED volatile functions should not be inlined +WITH cte_1 AS NOT MATERIALIZED (SELECT *, random() FROM test_table) +SELECT + count(*) +FROM + cte_1; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT key, value, other_value, random() AS random FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value, intermediate_result.random FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb, random double precision)) cte_1 +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 101 +(1 row) + +-- cte_1 should be able to inlined even if +-- it is used one level below +WITH cte_1 AS (SELECT * FROM test_table) +SELECT + count(*) +FROM +( + WITH ct2 AS (SELECT * FROM cte_1) + SELECT * FROM ct2 +) as foo; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE ct2 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 101 +(1 row) + +-- a similar query, but there is also +-- one more cte, which relies on the previous +-- CTE +WITH cte_1 AS (SELECT * FROM test_table) +SELECT + count(DISTINCT key) +FROM +( + WITH cte_2 AS (SELECT * FROM cte_1), + cte_3 AS (SELECT * FROM cte_2) + SELECT * FROM cte_3 +) as foo; +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: CTE cte_3 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- inlined CTE contains a reference to outer query +-- should be fine (because we pushdown the whole query) +SELECT count(*) + FROM + (SELECT * + FROM test_table) AS test_table_cte + JOIN LATERAL + (WITH bar AS (SELECT * + FROM test_table + WHERE key = test_table_cte.key) + SELECT * + FROM + bar + LEFT JOIN test_table u2 ON u2.key = bar.key) AS foo ON TRUE; +DEBUG: CTE bar is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 10331 +(1 row) + +-- inlined CTE contains a reference to outer query +-- should be fine (even if the recursive planning fails +-- to recursively plan the query) +SELECT count(*) + FROM + (SELECT * + FROM test_table) AS test_table_cte + JOIN LATERAL + (WITH bar AS (SELECT * + FROM test_table + WHERE key = test_table_cte.key) + SELECT * + FROM + bar + LEFT JOIN test_table u2 ON u2.key = bar.value::int) AS foo ON TRUE; +DEBUG: CTE bar is going to be inlined via distributed planning +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 +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +DEBUG: Router planner cannot handle multi-shard select queries +ERROR: CTEs that refer to other subqueries are not supported in multi-shard queries +-- inlined CTE can recursively planned later, that's the decision +-- recursive planning makes +-- LIMIT 5 in cte2 triggers recusrive planning, after cte inlining +WITH cte_1 AS (SELECT * FROM test_table) +SELECT + * +FROM +( + WITH ct2 AS (SELECT * FROM cte_1 ORDER BY 1, 2, 3 LIMIT 5) + SELECT * FROM ct2 +) as foo ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 5; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE ct2 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 5 +DEBUG: generating subplan XXX_1 for subquery SELECT key, value, other_value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1 ORDER BY key, value, other_value LIMIT 5 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, value, other_value FROM (SELECT ct2.key, ct2.value, ct2.other_value FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) ct2) foo ORDER BY key DESC, value DESC, other_value DESC LIMIT 5 +DEBUG: Creating router plan + key | value | other_value +--------------------------------------------------------------------- + 0 | test30 | {"f1": 30, "f2": 540, "f3": "test30"} + 0 | test20 | {"f1": 20, "f2": 360, "f3": "test20"} + 0 | test100 | {"f1": 100, "f2": 1800, "f3": "test100"} + 0 | test10 | {"f1": 10, "f2": 180, "f3": "test10"} + 0 | test0 | {"f1": 0, "f2": 0, "f3": "test0"} +(5 rows) + +-- all nested CTEs can be inlinied +WITH cte_1 AS ( + WITH cte_1 AS ( + WITH cte_1 AS ( + WITH cte_1 AS ( + WITH cte_1 AS ( + WITH cte_1 AS ( + WITH cte_1 AS (SELECT count(*), key FROM test_table GROUP BY key) + SELECT * FROM cte_1) + SELECT * FROM cte_1 WHERE key = 1) + SELECT * FROM cte_1 WHERE key = 2) + SELECT * FROM cte_1 WHERE key = 3) + SELECT * FROM cte_1 WHERE key = 4) + SELECT * FROM cte_1 WHERE key = 5) +SELECT * FROM cte_1 WHERE key = 6; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 + count | key +--------------------------------------------------------------------- +(0 rows) + +-- ctes can be inlined even if they are used +-- in set operations +WITH cte_1 AS (SELECT * FROM test_table), + cte_2 AS (SELECT * FROM test_table) +SELECT count(*) FROM ( +(SELECT * FROM cte_1 EXCEPT SELECT * FROM test_table) +UNION +(SELECT * FROM cte_2)) as foo; +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: 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 key, value, other_value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_2 for subquery SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_3 for subquery SELECT key, value, other_value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_2 +DEBUG: Creating router plan +DEBUG: generating subplan XXX_4 for subquery (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb) EXCEPT SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) UNION SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) foo +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 101 +(1 row) + +-- cte_1 is going to be inlined even inside another set operation +WITH cte_1 AS (SELECT * FROM test_table), + cte_2 AS (SELECT * FROM test_table ORDER BY 1 DESC LIMIT 3) +(SELECT *, (SELECT 1) FROM cte_1 EXCEPT SELECT *, 1 FROM test_table) +UNION +(SELECT *, 1 FROM cte_2) +ORDER BY 1,2; +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: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 +DEBUG: generating subplan XXX_1 for subquery SELECT key, value, other_value FROM cte_inline.test_table ORDER BY key DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_2 for subquery SELECT key, value, other_value, (SELECT 1) FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_3 for subquery SELECT key, value, other_value, 1 FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value, intermediate_result."?column?" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb, "?column?" integer) EXCEPT SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value, intermediate_result."?column?" FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb, "?column?" integer)) UNION SELECT cte_2.key, cte_2.value, cte_2.other_value, 1 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_2 ORDER BY 1, 2 +DEBUG: Creating router plan + key | value | other_value | ?column? +--------------------------------------------------------------------- + 9 | test19 | {"f1": 19, "f2": 342, "f3": "test19"} | 1 + 9 | test29 | {"f1": 29, "f2": 522, "f3": "test29"} | 1 + 9 | test9 | {"f1": 9, "f2": 162, "f3": "test9"} | 1 +(3 rows) + +-- cte_1 is safe to inline, even if because after inlining +-- it'd be in a query tree where there is a query that is +-- not supported by Citus unless recursively planned +-- cte_2 is on another queryTree, should be fine +WITH cte_1 AS (SELECT * FROM test_table), + cte_2 AS (SELECT * FROM test_table) +(SELECT *, (SELECT key FROM cte_1) FROM test_table) +UNION +(SELECT *, 1 FROM cte_2); +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: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_2 for CTE cte_2: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_3 for subquery SELECT key, value, other_value, (SELECT cte_1.key FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1) AS key FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value, intermediate_result.key_1 AS key FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb, key_1 integer) UNION SELECT cte_2.key, cte_2.value, cte_2.other_value, 1 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_2 +DEBUG: Creating router plan +ERROR: more than one row returned by a subquery used as an expression +CONTEXT: while executing command on localhost:xxxxx +-- after inlining CTEs, the query becomes +-- subquery pushdown with set operations +WITH cte_1 AS (SELECT * FROM test_table), + cte_2 AS (SELECT * FROM test_table) +SELECT max(key) FROM +( + SELECT * FROM cte_1 + UNION + SELECT * FROM cte_2 +) as bar; +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: Router planner cannot handle multi-shard select queries + max +--------------------------------------------------------------------- + 9 +(1 row) + +-- cte LEFT JOIN subquery should only work +-- when CTE is inlined, as Citus currently +-- doesn't know how to handle intermediate +-- results in the outer parts of outer +-- queries +WITH cte AS (SELECT * FROM test_table) +SELECT + count(*) +FROM + cte LEFT JOIN test_table USING (key); +DEBUG: CTE cte is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 1021 +(1 row) + +-- the CTEs are very simple, so postgres +-- can pull-up the subqueries after inlining +-- the CTEs, and the query that we send to workers +-- becomes a join between two tables +WITH cte_1 AS (SELECT key FROM test_table), + cte_2 AS (SELECT key FROM test_table) +SELECT + count(*) +FROM + cte_1 JOIN cte_2 USING (key); +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: Router planner cannot handle multi-shard select queries +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [-1073741824,-1] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [0,1073741823] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [1073741824,2147483647] +DEBUG: join prunable for intervals [-1073741824,-1] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [-1073741824,-1] and [0,1073741823] +DEBUG: join prunable for intervals [-1073741824,-1] and [1073741824,2147483647] +DEBUG: join prunable for intervals [0,1073741823] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [0,1073741823] and [-1073741824,-1] +DEBUG: join prunable for intervals [0,1073741823] and [1073741824,2147483647] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-1073741824,-1] +DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] + count +--------------------------------------------------------------------- + 1021 +(1 row) + +-- the following query is kind of interesting +-- During INSERT .. SELECT via coordinator, +-- Citus moves the CTEs into SELECT part, and plans/execute +-- the SELECT separately. Thus, fist_table_cte can be inlined +-- by Citus -- but not by Postgres +WITH fist_table_cte AS + (SELECT * FROM test_table) +INSERT INTO test_table + (key, value) + SELECT + key, value + FROM + fist_table_cte; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: CTE fist_table_cte is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'key' +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960000 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960001 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960002 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960003 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1960003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer, value text) +-- the following INSERT..SELECT is even more interesting +-- the CTE becomes pushdownable +INSERT INTO test_table +WITH fist_table_cte AS + (SELECT * FROM test_table) + SELECT + key, value + FROM + fist_table_cte; +DEBUG: CTE fist_table_cte is going to be inlined via distributed planning +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960000 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960000 test_table) fist_table_cte WHERE (key IS NOT NULL) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960001 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960001 test_table) fist_table_cte WHERE (key IS NOT NULL) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960002 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960002 test_table) fist_table_cte WHERE (key IS NOT NULL) +DEBUG: distributed statement: INSERT INTO cte_inline.test_table_1960003 AS citus_table_alias (key, value) SELECT key, value FROM (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table_1960003 test_table) fist_table_cte WHERE (key IS NOT NULL) +-- update/delete/modifying ctes +-- we don't support any cte inlining in modifications +-- queries and modifying CTEs +WITH cte_1 AS (SELECT * FROM test_table) + DELETE FROM test_table WHERE key NOT IN (SELECT key FROM cte_1); +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 key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM cte_inline.test_table WHERE (NOT (key OPERATOR(pg_catalog.=) ANY (SELECT cte_1.key FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1))) +DEBUG: Creating router plan +-- NOT MATERIALIZED should not CTEs that are used in a modifying query, because +-- we de still don't support it +WITH cte_1 AS NOT MATERIALIZED (SELECT * FROM test_table) + DELETE FROM test_table WHERE key NOT IN (SELECT key FROM cte_1); +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 key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM cte_inline.test_table WHERE (NOT (key OPERATOR(pg_catalog.=) ANY (SELECT cte_1.key FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1))) +DEBUG: Creating router plan +-- we don't inline CTEs if they are modifying CTEs +WITH cte_1 AS (DELETE FROM test_table WHERE key % 3 = 1 RETURNING key) +SELECT * FROM cte_1 ORDER BY 1 DESC LIMIT 3; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: DELETE FROM cte_inline.test_table WHERE ((key OPERATOR(pg_catalog.%) 3) OPERATOR(pg_catalog.=) 1) RETURNING key +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_1 ORDER BY key DESC LIMIT 3 +DEBUG: Creating router plan + key +--------------------------------------------------------------------- + 7 + 7 + 7 +(3 rows) + +-- NOT MATERIALIZED should not affect modifying CTEs +WITH cte_1 AS NOT MATERIALIZED (DELETE FROM test_table WHERE key % 3 = 0 RETURNING key) +SELECT count(*) FROM cte_1; +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: DELETE FROM cte_inline.test_table WHERE ((key OPERATOR(pg_catalog.%) 3) OPERATOR(pg_catalog.=) 0) RETURNING key +DEBUG: Creating router plan +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_1 +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 164 +(1 row) + +-- cte with column aliases +SELECT * FROM test_table, +(WITH cte_1 (x,y) AS (SELECT * FROM test_table), + cte_2 (z,y) AS (SELECT value, other_value, key FROM test_table), + cte_3 (t,m) AS (SELECT z, y, key as cte_2_key FROM cte_2) + SELECT * FROM cte_2, cte_3) as bar +ORDER BY value, other_value, z, y, t, m, cte_2_key +LIMIT 5; +DEBUG: CTE cte_3 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_2: SELECT value, other_value, key FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.key, test_table.value, test_table.other_value, bar.z, bar.y, bar.key, bar.t, bar.m, bar.cte_2_key FROM cte_inline.test_table, (SELECT cte_2.z, cte_2.y, cte_2.key, cte_3.t, cte_3.m, cte_3.cte_2_key FROM (SELECT intermediate_result.value AS z, intermediate_result.other_value AS y, intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text, other_value jsonb, key integer)) cte_2, (SELECT cte_2_1.z AS t, cte_2_1.y AS m, cte_2_1.key AS cte_2_key FROM (SELECT intermediate_result.value AS z, intermediate_result.other_value AS y, intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text, other_value jsonb, key integer)) cte_2_1) cte_3) bar ORDER BY test_table.value, test_table.other_value, bar.z, bar.y, bar.t, bar.m, bar.cte_2_key LIMIT 5 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 5 + key | value | other_value | z | y | key | t | m | cte_2_key +--------------------------------------------------------------------- + 2 | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | 2 | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | 2 + 2 | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | 2 | test12 | | 2 + 2 | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | 2 | test12 | | 2 + 2 | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | 2 | test12 | | 2 + 2 | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | test12 | {"f1": 12, "f2": 216, "f3": "test12"} | 2 | test15 | {"f1": 15, "f2": 270, "f3": "test15"} | 5 +(5 rows) + +-- cte used in HAVING subquery just works fine +-- even if it is inlined +WITH cte_1 AS (SELECT max(key) as max FROM test_table) +SELECT + key, count(*) +FROM + test_table +GROUP BY + key +HAVING + (count(*) > (SELECT max FROM cte_1)) +ORDER BY 2 DESC, 1 DESC +LIMIT 5; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 max(key) AS max FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, count(*) AS count FROM cte_inline.test_table GROUP BY key HAVING (count(*) OPERATOR(pg_catalog.>) (SELECT cte_1.max FROM (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max integer)) cte_1)) ORDER BY (count(*)) DESC, key DESC LIMIT 5 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 5 + key | count +--------------------------------------------------------------------- + 8 | 40 + 5 | 40 + 2 | 40 +(3 rows) + +-- cte used in ORDER BY just works fine +-- even if it is inlined +WITH cte_1 AS (SELECT max(key) as max FROM test_table) +SELECT + key +FROM + test_table JOIN cte_1 ON (key = max) +ORDER BY + cte_1.max +LIMIT 3; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 max(key) AS max FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.key FROM (cte_inline.test_table JOIN (SELECT intermediate_result.max FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(max integer)) cte_1 ON ((test_table.key OPERATOR(pg_catalog.=) cte_1.max))) ORDER BY cte_1.max LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + key +--------------------------------------------------------------------- + 8 + 8 + 8 +(3 rows) + +PREPARE inlined_cte_without_params AS + WITH cte_1 AS (SELECT count(*) FROM test_table GROUP BY key) + SELECT * FROM cte_1 ORDER BY 1 DESC LIMIT 3; +PREPARE non_inlined_cte_without_params AS + WITH cte_1 AS (SELECT * FROM test_table) + SELECT + * + FROM + test_table LEFT JOIN cte_1 USING (value) ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 3; +PREPARE inlined_cte_has_parameter_on_non_dist_key(text) AS + WITH cte_1 AS (SELECT count(*) FROM test_table WHERE value = $1 GROUP BY key) + SELECT * FROM cte_1 ORDER BY 1 DESC LIMIT 3; +PREPARE inlined_cte_has_parameter_on_dist_key(int) AS + WITH cte_1 AS (SELECT count(*) FROM test_table WHERE key > $1 GROUP BY key) + SELECT * FROM cte_1 ORDER BY 1 DESC LIMIT 3; +PREPARE non_inlined_cte_has_parameter_on_dist_key(int) AS + WITH cte_1 AS (SELECT * FROM test_table where key > $1) + SELECT + * + FROM + test_table LEFT JOIN cte_1 USING (value) ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 3; +PREPARE retry_planning(int) AS + WITH cte_1 AS (SELECT * FROM test_table WHERE key > $1) + SELECT json_object_agg(DISTINCT key, value) FROM cte_1 ORDER BY max(key), min(value) DESC LIMIT 3; +EXECUTE inlined_cte_without_params; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 40 + 40 + 40 +(3 rows) + +EXECUTE inlined_cte_without_params; + count +--------------------------------------------------------------------- + 40 + 40 + 40 +(3 rows) + +EXECUTE inlined_cte_without_params; + count +--------------------------------------------------------------------- + 40 + 40 + 40 +(3 rows) + +EXECUTE inlined_cte_without_params; + count +--------------------------------------------------------------------- + 40 + 40 + 40 +(3 rows) + +EXECUTE inlined_cte_without_params; + count +--------------------------------------------------------------------- + 40 + 40 + 40 +(3 rows) + +EXECUTE inlined_cte_without_params; + count +--------------------------------------------------------------------- + 40 + 40 + 40 +(3 rows) + +EXECUTE non_inlined_cte_without_params; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC, test_table.key DESC, test_table.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_without_params; + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_without_params; + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_without_params; + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_without_params; + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_without_params; + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE inlined_cte_has_parameter_on_non_dist_key('test1'); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE inlined_cte_has_parameter_on_non_dist_key('test2'); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 4 +(1 row) + +EXECUTE inlined_cte_has_parameter_on_non_dist_key('test3'); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE inlined_cte_has_parameter_on_non_dist_key('test4'); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE inlined_cte_has_parameter_on_non_dist_key('test5'); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 4 +(1 row) + +EXECUTE inlined_cte_has_parameter_on_non_dist_key('test6'); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- +(0 rows) + +EXECUTE inlined_cte_has_parameter_on_dist_key(1); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 40 + 40 + 40 +(3 rows) + +EXECUTE inlined_cte_has_parameter_on_dist_key(2); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 40 + 40 +(2 rows) + +EXECUTE inlined_cte_has_parameter_on_dist_key(3); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 40 + 40 +(2 rows) + +EXECUTE inlined_cte_has_parameter_on_dist_key(4); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 40 + 40 +(2 rows) + +EXECUTE inlined_cte_has_parameter_on_dist_key(5); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 40 +(1 row) + +EXECUTE inlined_cte_has_parameter_on_dist_key(6); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + count +--------------------------------------------------------------------- + 40 +(1 row) + +EXECUTE non_inlined_cte_has_parameter_on_dist_key(1); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 1) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC, test_table.key DESC, test_table.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_has_parameter_on_dist_key(2); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 2) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC, test_table.key DESC, test_table.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_has_parameter_on_dist_key(3); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 3) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC, test_table.key DESC, test_table.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_has_parameter_on_dist_key(4); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 4) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC, test_table.key DESC, test_table.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_has_parameter_on_dist_key(5); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 5) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC, test_table.key DESC, test_table.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE non_inlined_cte_has_parameter_on_dist_key(6); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: CTE cte_1 is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 6) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT test_table.value, test_table.key, test_table.other_value, cte_1.key, cte_1.other_value FROM (cte_inline.test_table LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_1 USING (value)) ORDER BY test_table.value DESC, test_table.key DESC, test_table.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + value | key | other_value | key | other_value +--------------------------------------------------------------------- + test98 | 8 | | 8 | + test98 | 8 | | 8 | + test98 | 8 | | 8 | +(3 rows) + +EXECUTE retry_planning(1); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + json_object_agg +--------------------------------------------------------------------- + { "2" : "test12", "2" : "test2", "2" : "test22", "2" : "test32", "2" : "test42", "2" : "test52", "2" : "test62", "2" : "test72", "2" : "test82", "2" : "test92", "5" : "test15", "5" : "test25", "5" : "test35", "5" : "test45", "5" : "test5", "5" : "test55", "5" : "test65", "5" : "test75", "5" : "test85", "5" : "test95", "8" : "test18", "8" : "test28", "8" : "test38", "8" : "test48", "8" : "test58", "8" : "test68", "8" : "test78", "8" : "test8", "8" : "test88", "8" : "test98" } +(1 row) + +EXECUTE retry_planning(2); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + json_object_agg +--------------------------------------------------------------------- + { "5" : "test15", "5" : "test25", "5" : "test35", "5" : "test45", "5" : "test5", "5" : "test55", "5" : "test65", "5" : "test75", "5" : "test85", "5" : "test95", "8" : "test18", "8" : "test28", "8" : "test38", "8" : "test48", "8" : "test58", "8" : "test68", "8" : "test78", "8" : "test8", "8" : "test88", "8" : "test98" } +(1 row) + +EXECUTE retry_planning(3); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + json_object_agg +--------------------------------------------------------------------- + { "5" : "test15", "5" : "test25", "5" : "test35", "5" : "test45", "5" : "test5", "5" : "test55", "5" : "test65", "5" : "test75", "5" : "test85", "5" : "test95", "8" : "test18", "8" : "test28", "8" : "test38", "8" : "test48", "8" : "test58", "8" : "test68", "8" : "test78", "8" : "test8", "8" : "test88", "8" : "test98" } +(1 row) + +EXECUTE retry_planning(4); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + json_object_agg +--------------------------------------------------------------------- + { "5" : "test15", "5" : "test25", "5" : "test35", "5" : "test45", "5" : "test5", "5" : "test55", "5" : "test65", "5" : "test75", "5" : "test85", "5" : "test95", "8" : "test18", "8" : "test28", "8" : "test38", "8" : "test48", "8" : "test58", "8" : "test68", "8" : "test78", "8" : "test8", "8" : "test88", "8" : "test98" } +(1 row) + +EXECUTE retry_planning(5); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + json_object_agg +--------------------------------------------------------------------- + { "8" : "test18", "8" : "test28", "8" : "test38", "8" : "test48", "8" : "test58", "8" : "test68", "8" : "test78", "8" : "test8", "8" : "test88", "8" : "test98" } +(1 row) + +EXECUTE retry_planning(6); +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + json_object_agg +--------------------------------------------------------------------- + { "8" : "test18", "8" : "test28", "8" : "test38", "8" : "test48", "8" : "test58", "8" : "test68", "8" : "test78", "8" : "test8", "8" : "test88", "8" : "test98" } +(1 row) + +-- this test can only work if the CTE is recursively +-- planned +WITH b AS (SELECT * FROM test_table) +SELECT count(*) FROM (SELECT key as x FROM test_table OFFSET 0) as ref LEFT JOIN b ON (ref.x = b.key); +DEBUG: CTE b is going to be inlined via distributed planning +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 key AS x FROM cte_inline.test_table OFFSET 0 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.x FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) ref LEFT JOIN (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) b ON ((ref.x OPERATOR(pg_catalog.=) b.key))) +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE b: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_2 for subquery SELECT key AS x FROM cte_inline.test_table OFFSET 0 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.x FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(x integer)) ref LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) b ON ((ref.x OPERATOR(pg_catalog.=) b.key))) +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 4800 +(1 row) + +-- this becomes a non-colocated subquery join +-- because after the CTEs are inlined the joins +-- become a non-colocated subquery join +WITH a AS (SELECT * FROM test_table), +b AS (SELECT * FROM test_table) +SELECT count(*) FROM a LEFT JOIN b ON (a.value = b.value); +DEBUG: CTE a is going to be inlined via distributed planning +DEBUG: CTE b is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) a LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) b ON ((a.value OPERATOR(pg_catalog.=) b.value))) +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 480 +(1 row) + +-- cte a has to be recursively planned because of OFFSET 0 +-- after that, cte b also requires recursive planning +WITH a AS (SELECT * FROM test_table OFFSET 0), +b AS (SELECT * FROM test_table) +SELECT min(a.key) FROM a LEFT JOIN b ON (a.value = b.value); +DEBUG: CTE a is going to be inlined via distributed planning +DEBUG: CTE b is going to be inlined via distributed planning +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 key, value, other_value FROM cte_inline.test_table OFFSET 0 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT min(a.key) AS min FROM ((SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) a LEFT JOIN (SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) b ON ((a.value OPERATOR(pg_catalog.=) b.value))) +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE a: SELECT key, value, other_value FROM cte_inline.test_table OFFSET 0 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_2 for CTE b: SELECT key, value, other_value FROM cte_inline.test_table +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT min(a.key) AS min FROM ((SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) a LEFT JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) b ON ((a.value OPERATOR(pg_catalog.=) b.value))) +DEBUG: Creating router plan + min +--------------------------------------------------------------------- + 2 +(1 row) + +-- after both CTEs are inlined, this becomes non-colocated subquery join +WITH cte_1 AS (SELECT * FROM test_table), +cte_2 AS (SELECT * FROM test_table) +SELECT * FROM cte_1 JOIN cte_2 ON (cte_1.value > cte_2.value) ORDER BY 1,2,3,4,5,6 DESC LIMIT 3;; +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: 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 key, value, other_value FROM cte_inline.test_table +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT cte_1.key, cte_1.value, cte_1.other_value, cte_2.key, cte_2.value, cte_2.other_value FROM ((SELECT test_table.key, test_table.value, test_table.other_value FROM cte_inline.test_table) cte_1 JOIN (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.other_value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, other_value jsonb)) cte_2 ON ((cte_1.value OPERATOR(pg_catalog.>) cte_2.value))) ORDER BY cte_1.key, cte_1.value, cte_1.other_value, cte_2.key, cte_2.value, cte_2.other_value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: push down of limit count: 3 + key | value | other_value | key | value | other_value +--------------------------------------------------------------------- + 2 | test2 | {"f1": 2, "f2": 36, "f3": "test2"} | 2 | test12 | + 2 | test2 | {"f1": 2, "f2": 36, "f3": "test2"} | 2 | test12 | + 2 | test2 | {"f1": 2, "f2": 36, "f3": "test2"} | 2 | test12 | +(3 rows) + +-- full join is only supported when both sides are +-- recursively planned +WITH cte_1 AS (SELECT value FROM test_table WHERE key > 1), + cte_2 AS (SELECT value FROM test_table WHERE key > 3) +SELECT * FROM cte_1 FULL JOIN cte_2 USING (value) ORDER BY 1 DESC LIMIT 3;; +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: 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 value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 3) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT value FROM ((SELECT test_table.value FROM cte_inline.test_table WHERE (test_table.key OPERATOR(pg_catalog.>) 1)) cte_1 FULL JOIN (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) cte_2 USING (value)) ORDER BY value DESC LIMIT 3 +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE cte_1: SELECT value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 1) +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_2 for CTE cte_2: SELECT value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 3) +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT value FROM ((SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) cte_1 FULL JOIN (SELECT intermediate_result.value FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(value text)) cte_2 USING (value)) ORDER BY value DESC LIMIT 3 +DEBUG: Creating router plan + value +--------------------------------------------------------------------- + test98 + test98 + test98 +(3 rows) + +-- an unsupported agg. for multi-shard queries +-- so CTE has to be recursively planned +WITH cte_1 AS (SELECT * FROM test_table WHERE key > 1) +SELECT json_object_agg(DISTINCT key, value) FROM cte_1; +DEBUG: CTE cte_1 is going to be inlined via distributed planning +DEBUG: Router planner cannot handle multi-shard select queries + json_object_agg +--------------------------------------------------------------------- + { "2" : "test12", "2" : "test2", "2" : "test22", "2" : "test32", "2" : "test42", "2" : "test52", "2" : "test62", "2" : "test72", "2" : "test82", "2" : "test92", "5" : "test15", "5" : "test25", "5" : "test35", "5" : "test45", "5" : "test5", "5" : "test55", "5" : "test65", "5" : "test75", "5" : "test85", "5" : "test95", "8" : "test18", "8" : "test28", "8" : "test38", "8" : "test48", "8" : "test58", "8" : "test68", "8" : "test78", "8" : "test8", "8" : "test88", "8" : "test98" } +(1 row) + +-- both cte_1 and cte_2 are going to be inlined. +-- later, cte_2 is recursively planned since it doesn't have +-- GROUP BY but aggragate in a subquery. +-- this is an important example of being able to recursively plan +-- "some" of the CTEs +WITH cte_1 AS (SELECT value FROM test_table WHERE key > 1), + cte_2 AS (SELECT max(value) as value FROM test_table WHERE key > 3) +SELECT count(*) FROM cte_1 JOIN cte_2 USING (value); +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: 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 max(value) AS value FROM cte_inline.test_table WHERE (key OPERATOR(pg_catalog.>) 3) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT test_table.value FROM cte_inline.test_table WHERE (test_table.key OPERATOR(pg_catalog.>) 1)) cte_1 JOIN (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text)) cte_2 USING (value)) +DEBUG: Router planner cannot handle multi-shard select queries + count +--------------------------------------------------------------------- + 4 +(1 row) + +-- prevent DROP CASCADE to give notices +SET client_min_messages TO ERROR; +DROP SCHEMA cte_inline CASCADE; diff --git a/src/test/regress/expected/distributed_triggers.out b/src/test/regress/expected/distributed_triggers.out index 002c4e465..c53b91acb 100644 --- a/src/test/regress/expected/distributed_triggers.out +++ b/src/test/regress/expected/distributed_triggers.out @@ -733,33 +733,29 @@ SELECT operation_type, product_sku, state_code FROM record_sale ORDER BY 1,2,3; -- --Test ALTER TRIGGER -- +-- Pre PG15, renaming the trigger on the parent table didn't rename the same trigger on +-- the children as well. Hence, let's not print the trigger names of the children +-- In PG15, rename is consistent for all partitions of the parent +-- This is tested in pg15.sql file. CREATE VIEW sale_triggers AS SELECT tgname, tgrelid::regclass, tgenabled FROM pg_trigger - WHERE tgrelid::regclass::text like 'sale%' + WHERE tgrelid::regclass::text = 'sale' ORDER BY 1, 2; SELECT * FROM sale_triggers ORDER BY 1,2; tgname | tgrelid | tgenabled --------------------------------------------------------------------- record_sale_trigger | sale | O - record_sale_trigger | sale_newyork | O - record_sale_trigger | sale_california | O truncate_trigger_xxxxxxx | sale | O - truncate_trigger_xxxxxxx | sale_california | O - truncate_trigger_xxxxxxx | sale_newyork | O -(6 rows) +(2 rows) ALTER TRIGGER "record_sale_trigger" ON "distributed_triggers"."sale" RENAME TO "new_record_sale_trigger"; SELECT * FROM sale_triggers ORDER BY 1,2; tgname | tgrelid | tgenabled --------------------------------------------------------------------- new_record_sale_trigger | sale | O - record_sale_trigger | sale_newyork | O - record_sale_trigger | sale_california | O truncate_trigger_xxxxxxx | sale | O - truncate_trigger_xxxxxxx | sale_california | O - truncate_trigger_xxxxxxx | sale_newyork | O -(6 rows) +(2 rows) CREATE EXTENSION seg; ALTER TRIGGER "emptest_audit" ON "emptest" DEPENDS ON EXTENSION seg; diff --git a/src/test/regress/expected/drop_partitioned_table.out b/src/test/regress/expected/drop_partitioned_table.out index 2cfd6a7b7..660adb89c 100644 --- a/src/test/regress/expected/drop_partitioned_table.out +++ b/src/test/regress/expected/drop_partitioned_table.out @@ -326,6 +326,7 @@ SET search_path = drop_partitioned_table; SET citus.shard_count TO 1; SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 727000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 1344400; DROP EVENT TRIGGER new_trigger_for_drops; -- Case 1 - we should skip CREATE TABLE parent (x text, t timestamptz DEFAULT now()) PARTITION BY RANGE (t); @@ -353,6 +354,8 @@ NOTICE: issuing SELECT worker_drop_distributed_table('drop_partitioned_table.pa NOTICE: issuing DROP TABLE IF EXISTS drop_partitioned_table.parent_xxxxx CASCADE NOTICE: issuing SELECT worker_drop_distributed_table('drop_partitioned_table.child1') NOTICE: issuing SELECT worker_drop_distributed_table('drop_partitioned_table.child1') +NOTICE: issuing SELECT pg_catalog.citus_internal_delete_colocation_metadata(1344400) +NOTICE: issuing SELECT pg_catalog.citus_internal_delete_colocation_metadata(1344400) ROLLBACK; NOTICE: issuing ROLLBACK NOTICE: issuing ROLLBACK @@ -374,6 +377,8 @@ NOTICE: issuing DROP TABLE IF EXISTS drop_partitioned_table.parent_xxxxx CASCAD NOTICE: issuing SELECT worker_drop_distributed_table('drop_partitioned_table.child1') NOTICE: issuing SELECT worker_drop_distributed_table('drop_partitioned_table.child1') NOTICE: issuing DROP TABLE IF EXISTS drop_partitioned_table.child1_xxxxx CASCADE +NOTICE: issuing SELECT pg_catalog.citus_internal_delete_colocation_metadata(1344400) +NOTICE: issuing SELECT pg_catalog.citus_internal_delete_colocation_metadata(1344400) ROLLBACK; NOTICE: issuing ROLLBACK NOTICE: issuing ROLLBACK diff --git a/src/test/regress/expected/failure_connection_establishment.out b/src/test/regress/expected/failure_connection_establishment.out index 9c44269a3..d032755dd 100644 --- a/src/test/regress/expected/failure_connection_establishment.out +++ b/src/test/regress/expected/failure_connection_establishment.out @@ -34,24 +34,6 @@ SELECT create_distributed_table('products', 'product_no'); ALTER TABLE products ADD CONSTRAINT p_key PRIMARY KEY(name); ERROR: cannot create constraint on "products" DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). --- we will insert a connection delay here as this query was the cause for an investigation --- into connection establishment problems -SET citus.node_connection_timeout TO 400; -SELECT citus.mitmproxy('conn.delay(500)'); - mitmproxy ---------------------------------------------------------------------- - -(1 row) - -ALTER TABLE products ADD CONSTRAINT p_key PRIMARY KEY(product_no); -WARNING: could not establish connection after 400 ms -ERROR: connection to the remote node localhost:xxxxx failed -SELECT citus.mitmproxy('conn.allow()'); - mitmproxy ---------------------------------------------------------------------- - -(1 row) - CREATE TABLE r1 ( id int PRIMARY KEY, name text @@ -70,77 +52,88 @@ HINT: To remove the local data, run: SELECT truncate_local_data_after_distribut (1 row) -SELECT citus.clear_network_traffic(); - clear_network_traffic +-- Confirm that the first placement for both tables is on the second worker +-- node. This is necessary so we can use the first-replica task assignment +-- policy to first hit the node that we generate timeouts for. +SELECT placementid, p.shardid, logicalrelid, LEAST(2, groupid) groupid +FROM pg_dist_placement p JOIN pg_dist_shard s ON p.shardid = s.shardid +ORDER BY placementid; + placementid | shardid | logicalrelid | groupid --------------------------------------------------------------------- + 1450000 | 1450000 | products | 2 + 1450001 | 1450000 | products | 1 + 1450002 | 1450001 | products | 1 + 1450003 | 1450001 | products | 2 + 1450004 | 1450002 | products | 2 + 1450005 | 1450002 | products | 1 + 1450006 | 1450003 | products | 1 + 1450007 | 1450003 | products | 2 + 1450008 | 1450004 | r1 | 2 + 1450009 | 1450004 | r1 | 1 +(10 rows) -(1 row) - -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.task_assignment_policy TO 'first-replica'; +-- we will insert a connection delay here as this query was the cause for an +-- investigation into connection establishment problems +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); mitmproxy --------------------------------------------------------------------- (1 row) --- we cannot control which replica of the reference table will be queried and there is --- only one specific client we can control the connection for. --- by using round-robin task_assignment_policy we can force to hit both machines. --- and in the end, dumping the network traffic shows that the connection establishment --- is initiated to the node behind the proxy -SET client_min_messages TO ERROR; -SET citus.task_assignment_policy TO 'round-robin'; --- suppress the warning since we can't control which shard is chose first. Failure of this --- test would be if one of the queries does not return the result but an error. -SELECT name FROM r1 WHERE id = 2; - name ---------------------------------------------------------------------- - bar -(1 row) - -SELECT name FROM r1 WHERE id = 2; - name ---------------------------------------------------------------------- - bar -(1 row) - --- verify a connection attempt was made to the intercepted node, this would have cause the --- connection to have been delayed and thus caused a timeout -SELECT * FROM citus.dump_network_traffic() WHERE conn=0; - conn | source | message ---------------------------------------------------------------------- - 0 | coordinator | [initial message] -(1 row) - +ALTER TABLE products ADD CONSTRAINT p_key PRIMARY KEY(product_no); +WARNING: could not establish connection after 900 ms +ERROR: connection to the remote node localhost:xxxxx failed +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- (1 row) --- similar test with the above but this time on a --- distributed table instead of a reference table --- and with citus.force_max_query_parallelization is set -SET citus.force_max_query_parallelization TO ON; -SELECT citus.mitmproxy('conn.delay(500)'); +-- Make sure that we fall back to a working node for reads, even if it's not +-- the first choice in our task assignment policy. +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +-- tests for connectivity checks +SELECT name FROM r1 WHERE id = 2; +WARNING: could not establish any connections to the node localhost:xxxxx after 900 ms + name +--------------------------------------------------------------------- + bar +(1 row) + +RESET citus.node_connection_timeout; +SELECT citus.mitmproxy('conn.allow()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +-- similar test with the above but this time on a distributed table instead of +-- a reference table and with citus.force_max_query_parallelization is set +SET citus.force_max_query_parallelization TO ON; +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); mitmproxy --------------------------------------------------------------------- (1 row) --- suppress the warning since we can't control which shard is chose first. Failure of this --- test would be if one of the queries does not return the result but an error. -SELECT count(*) FROM products; - count ---------------------------------------------------------------------- - 0 -(1 row) - SELECT count(*) FROM products; +WARNING: could not establish any connections to the node localhost:xxxxx after 900 ms count --------------------------------------------------------------------- 0 (1 row) +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- @@ -158,24 +151,26 @@ SELECT create_distributed_table('single_replicatated', 'key'); -- this time the table is single replicated and we're still using the -- the max parallelization flag, so the query should fail SET citus.force_max_query_parallelization TO ON; -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); mitmproxy --------------------------------------------------------------------- (1 row) SELECT count(*) FROM single_replicatated; -ERROR: could not establish any connections to the node localhost:xxxxx after 400 ms -SET citus.force_max_query_parallelization TO OFF; --- one similar test, and this time on modification queries --- to see that connection establishement failures could --- fail the transaction (but not mark any placements as INVALID) +ERROR: could not establish any connections to the node localhost:xxxxx after 900 ms +RESET citus.force_max_query_parallelization; +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- (1 row) +-- one similar test, and this time on modification queries +-- to see that connection establishement failures could +-- fail the transaction (but not mark any placements as INVALID) BEGIN; SELECT count(*) as invalid_placement_count @@ -189,15 +184,23 @@ WHERE 0 (1 row) -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); mitmproxy --------------------------------------------------------------------- (1 row) INSERT INTO single_replicatated VALUES (100); -ERROR: could not establish any connections to the node localhost:xxxxx after 400 ms +ERROR: could not establish any connections to the node localhost:xxxxx after 900 ms COMMIT; +RESET citus.node_connection_timeout; +SELECT citus.mitmproxy('conn.allow()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + SELECT count(*) as invalid_placement_count FROM @@ -210,13 +213,6 @@ WHERE 0 (1 row) --- show that INSERT failed -SELECT citus.mitmproxy('conn.allow()'); - mitmproxy ---------------------------------------------------------------------- - -(1 row) - SELECT count(*) FROM single_replicatated WHERE key = 100; count --------------------------------------------------------------------- @@ -299,7 +295,8 @@ SELECT citus.mitmproxy('conn.onCommandComplete(command="SELECT 1").cancel(' || p SELECT * FROM citus_check_connection_to_node('localhost', :worker_2_proxy_port); ERROR: canceling statement due to user request -- verify that the checks are not successful when timeouts happen on a connection -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); mitmproxy --------------------------------------------------------------------- @@ -311,6 +308,13 @@ SELECT * FROM citus_check_connection_to_node('localhost', :worker_2_proxy_port); f (1 row) +RESET citus.node_connection_timeout; +SELECT citus.mitmproxy('conn.allow()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + -- tests for citus_check_cluster_node_health -- kill all connectivity checks that originate from this node SELECT citus.mitmproxy('conn.onQuery(query="^SELECT citus_check_connection_to_node").kill()'); @@ -392,15 +396,6 @@ SELECT * FROM citus_check_cluster_node_health(); localhost | 57637 | localhost | 57637 | t (4 rows) --- cancel all connections to this node -SELECT citus.mitmproxy('conn.onAuthenticationOk().cancel(' || pg_backend_pid() || ')'); - mitmproxy ---------------------------------------------------------------------- - -(1 row) - -SELECT * FROM citus_check_cluster_node_health(); -ERROR: canceling statement due to user request -- kill connection checks to this node SELECT citus.mitmproxy('conn.onQuery(query="^SELECT 1$").kill()'); mitmproxy @@ -427,13 +422,13 @@ SELECT citus.mitmproxy('conn.onQuery(query="^SELECT 1$").cancel(' || pg_backend_ SELECT * FROM citus_check_cluster_node_health(); ERROR: canceling statement due to user request RESET client_min_messages; +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- (1 row) -SET citus.node_connection_timeout TO DEFAULT; DROP SCHEMA fail_connect CASCADE; NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table products diff --git a/src/test/regress/expected/failure_create_distributed_table_concurrently.out b/src/test/regress/expected/failure_create_distributed_table_concurrently.out new file mode 100644 index 000000000..9c5d211e9 --- /dev/null +++ b/src/test/regress/expected/failure_create_distributed_table_concurrently.out @@ -0,0 +1,203 @@ +-- +-- failure_create_distributed_table_concurrently adds failure tests for creating distributed table concurrently without data. +-- +-- due to different libpq versions +-- some warning messages differ +-- between local and CI +SET client_min_messages TO ERROR; +-- setup db +CREATE SCHEMA IF NOT EXISTS create_dist_tbl_con; +SET SEARCH_PATH = create_dist_tbl_con; +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 +-- make sure coordinator is in the metadata +SELECT citus_set_coordinator_host('localhost', 57636); + citus_set_coordinator_host +--------------------------------------------------------------------- + +(1 row) + +-- create table that will be distributed concurrently +CREATE TABLE table_1 (id int PRIMARY KEY); +-- START OF TESTS +SELECT citus.mitmproxy('conn.allow()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +-- failure on shard table creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE create_dist_tbl_con.table_1").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: connection not open +CONTEXT: while executing command on localhost:xxxxx +-- cancellation on shard table creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE create_dist_tbl_con.table_1").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: canceling statement due to user request +-- failure on table constraints on replica identity creation +SELECT citus.mitmproxy('conn.onQuery(query="ALTER TABLE create_dist_tbl_con.table_1 ADD CONSTRAINT").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: connection not open +CONTEXT: while executing command on localhost:xxxxx +-- cancellation on table constraints on replica identity creation +SELECT citus.mitmproxy('conn.onQuery(query="ALTER TABLE create_dist_tbl_con.table_1 ADD CONSTRAINT").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: canceling statement due to user request +-- failure on subscription creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE SUBSCRIPTION").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: connection not open +CONTEXT: while executing command on localhost:xxxxx +-- cancellation on subscription creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE SUBSCRIPTION").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: canceling statement due to user request +-- failure on catching up LSN +SELECT citus.mitmproxy('conn.onQuery(query="SELECT min\(latest_end_lsn\) FROM pg_stat_subscription").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: connection not open +CONTEXT: while executing command on localhost:xxxxx +-- cancellation on catching up LSN +SELECT citus.mitmproxy('conn.onQuery(query="SELECT min\(latest_end_lsn\) FROM pg_stat_subscription").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: canceling statement due to user request +-- Comment out below flaky tests. It is caused by shard split cleanup which does not work properly yet. +-- -- failure on dropping subscription +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP SUBSCRIPTION").kill()'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); +-- -- cancellation on dropping subscription +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP SUBSCRIPTION").cancel(' || :pid || ')'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); +-- -- failure on dropping old shard +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS create_dist_tbl_con.table_1").kill()'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); +-- -- cancellation on dropping old shard +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS create_dist_tbl_con.table_1").cancel(' || :pid || ')'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); +-- failure on transaction begin +SELECT citus.mitmproxy('conn.onQuery(query="BEGIN").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: failure on connection marked as essential: localhost:xxxxx +-- failure on transaction begin +SELECT citus.mitmproxy('conn.onQuery(query="BEGIN").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: canceling statement due to user request +-- failure on transaction commit +SELECT citus.mitmproxy('conn.onQuery(query="COMMIT").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: failure on connection marked as essential: localhost:xxxxx +-- failure on transaction commit +SELECT citus.mitmproxy('conn.onQuery(query="COMMIT").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: canceling statement due to user request +-- failure on prepare transaction +SELECT citus.mitmproxy('conn.onQuery(query="PREPARE TRANSACTION").kill()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: connection not open +CONTEXT: while executing command on localhost:xxxxx +-- failure on prepare transaction +SELECT citus.mitmproxy('conn.onQuery(query="PREPARE TRANSACTION").cancel(' || :pid || ')'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table_concurrently('table_1', 'id'); +ERROR: canceling statement due to user request +-- END OF TESTS +SELECT citus.mitmproxy('conn.allow()'); + mitmproxy +--------------------------------------------------------------------- + +(1 row) + +-- Verify that the table can be distributed concurrently after unsuccessful attempts +SELECT create_distributed_table_concurrently('table_1', 'id'); + create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM pg_dist_shard WHERE logicalrelid = 'table_1'::regclass; + logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue +--------------------------------------------------------------------- + table_1 | 1880096 | t | -2147483648 | -1 + table_1 | 1880097 | t | 0 | 2147483647 +(2 rows) + +DROP SCHEMA create_dist_tbl_con CASCADE; +SET search_path TO default; +SELECT citus_remove_node('localhost', 57636); + citus_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/failure_create_distributed_table_non_empty.out b/src/test/regress/expected/failure_create_distributed_table_non_empty.out index ef7322d3b..076b1c96a 100644 --- a/src/test/regress/expected/failure_create_distributed_table_non_empty.out +++ b/src/test/regress/expected/failure_create_distributed_table_non_empty.out @@ -115,7 +115,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="^BEGIN TRANSACTION ISOLATION LEVEL R (1 row) SELECT create_distributed_table('test_table', 'id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- @@ -572,7 +574,7 @@ SELECT citus.mitmproxy('conn.kill()'); (1 row) SELECT create_distributed_table('test_table', 'id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +ERROR: failure on connection marked as essential: localhost:xxxxx SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- @@ -593,7 +595,7 @@ SELECT run_command_on_workers($$SELECT count(*) FROM information_schema.tables W (2 rows) -- in the first test, cancel the first connection we sent from the coordinator -SELECT citus.mitmproxy('conn.cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE").cancel(' || pg_backend_pid() || ')'); mitmproxy --------------------------------------------------------------------- @@ -637,7 +639,7 @@ SELECT citus.mitmproxy('conn.onQuery(query="^BEGIN TRANSACTION ISOLATION LEVEL R (1 row) SELECT create_distributed_table('test_table', 'id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +ERROR: failure on connection marked as essential: localhost:xxxxx SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- diff --git a/src/test/regress/expected/failure_create_reference_table.out b/src/test/regress/expected/failure_create_reference_table.out index a4cca6817..e7f2c44ac 100644 --- a/src/test/regress/expected/failure_create_reference_table.out +++ b/src/test/regress/expected/failure_create_reference_table.out @@ -25,7 +25,9 @@ SELECT citus.mitmproxy('conn.onQuery().kill()'); (1 row) SELECT create_reference_table('ref_table'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx SELECT count(*) FROM pg_dist_shard_placement; count --------------------------------------------------------------------- @@ -40,7 +42,9 @@ SELECT citus.mitmproxy('conn.onCommandComplete(command="BEGIN").kill()'); (1 row) SELECT create_reference_table('ref_table'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx SELECT count(*) FROM pg_dist_shard_placement; count --------------------------------------------------------------------- @@ -70,7 +74,9 @@ SELECT citus.mitmproxy('conn.onCommandComplete(command="SELECT 1").kill()'); (1 row) SELECT create_reference_table('ref_table'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx SELECT count(*) FROM pg_dist_shard_placement; count --------------------------------------------------------------------- @@ -171,8 +177,8 @@ SELECT create_reference_table('ref_table'); SELECT shardid, nodeport, shardstate FROM pg_dist_shard_placement ORDER BY shardid, nodeport; shardid | nodeport | shardstate --------------------------------------------------------------------- - 10000008 | 9060 | 1 - 10000008 | 57637 | 1 + 10000003 | 9060 | 1 + 10000003 | 57637 | 1 (2 rows) SET client_min_messages TO NOTICE; diff --git a/src/test/regress/expected/failure_create_table.out b/src/test/regress/expected/failure_create_table.out index af3bf48af..94225fc15 100644 --- a/src/test/regress/expected/failure_create_table.out +++ b/src/test/regress/expected/failure_create_table.out @@ -86,7 +86,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="^BEGIN TRANSACTION ISOLATION LEVEL R (1 row) SELECT create_distributed_table('test_table','id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- @@ -338,7 +340,9 @@ SELECT citus.mitmproxy('conn.kill()'); BEGIN; SELECT create_distributed_table('test_table','id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx ROLLBACK; SELECT citus.mitmproxy('conn.allow()'); mitmproxy @@ -372,7 +376,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="^BEGIN TRANSACTION ISOLATION LEVEL R BEGIN; SELECT create_distributed_table('test_table','id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx ROLLBACK; SELECT citus.mitmproxy('conn.allow()'); mitmproxy @@ -446,7 +452,9 @@ SELECT citus.mitmproxy('conn.kill()'); BEGIN; SELECT create_distributed_table('test_table','id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx ROLLBACK; SELECT citus.mitmproxy('conn.allow()'); mitmproxy @@ -510,7 +518,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="^BEGIN TRANSACTION ISOLATION LEVEL R BEGIN; SELECT create_distributed_table('test_table','id'); -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +WARNING: connection not open +CONTEXT: while executing command on localhost:xxxxx +ERROR: failure on connection marked as essential: localhost:xxxxx ROLLBACK; SELECT citus.mitmproxy('conn.allow()'); mitmproxy diff --git a/src/test/regress/expected/failure_ddl.out b/src/test/regress/expected/failure_ddl.out index 9658c9346..77b134a72 100644 --- a/src/test/regress/expected/failure_ddl.out +++ b/src/test/regress/expected/failure_ddl.out @@ -180,7 +180,7 @@ ALTER TABLE test_table DROP COLUMN new_column; -- but now kill just after the worker sends response to -- COMMIT command, so we'll have lots of warnings but the command -- should have been committed both on the distributed table and the placements -SET client_min_messages TO WARNING; +SET client_min_messages TO ERROR; SELECT citus.mitmproxy('conn.onCommandComplete(command="^COMMIT").kill()'); mitmproxy --------------------------------------------------------------------- @@ -188,23 +188,12 @@ SELECT citus.mitmproxy('conn.onCommandComplete(command="^COMMIT").kill()'); (1 row) ALTER TABLE test_table ADD COLUMN new_column INT; -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: failed to commit transaction on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: failed to commit transaction on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- (1 row) -SET client_min_messages TO ERROR; SELECT array_agg(name::text ORDER BY name::text) FROM public.table_attrs where relid = 'test_table'::regclass; array_agg --------------------------------------------------------------------- diff --git a/src/test/regress/expected/failure_failover_to_local_execution.out b/src/test/regress/expected/failure_failover_to_local_execution.out index 0ecc98111..56518141a 100644 --- a/src/test/regress/expected/failure_failover_to_local_execution.out +++ b/src/test/regress/expected/failure_failover_to_local_execution.out @@ -36,7 +36,7 @@ INSERT INTO failover_to_local SELECT i, i::text FROM generate_series(0,20)i; -- even if the connection establishment fails, Citus can -- failover to local exection SET citus.node_connection_timeout TO 400; -SELECT citus.mitmproxy('conn.delay(500)'); +SELECT citus.mitmproxy('conn.connect_delay(500)'); mitmproxy --------------------------------------------------------------------- @@ -54,6 +54,7 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM failure_fa (1 row) RESET client_min_messages; +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- @@ -68,7 +69,8 @@ CONTEXT: while executing command on localhost:xxxxx -- if the local execution is disabled, Citus does -- not try to fallback to local execution SET citus.enable_local_execution TO false; -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 400; +SELECT citus.mitmproxy('conn.connect_delay(500)'); mitmproxy --------------------------------------------------------------------- @@ -77,6 +79,7 @@ SELECT citus.mitmproxy('conn.delay(500)'); SET citus.log_local_commands TO ON; SELECT count(*) FROM failover_to_local; ERROR: could not establish any connections to the node localhost:xxxxx after 400 ms +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- diff --git a/src/test/regress/expected/failure_insert_select_repartition.out b/src/test/regress/expected/failure_insert_select_repartition.out index 0911ae5a8..d45318208 100644 --- a/src/test/regress/expected/failure_insert_select_repartition.out +++ b/src/test/regress/expected/failure_insert_select_repartition.out @@ -85,32 +85,42 @@ SELECT * FROM target_table ORDER BY a; (10 rows) -- --- kill fetch_intermediate_results +-- kill the COPY command that's created by fetch_intermediate_results -- this fails the fetch into target, so source replication doesn't matter -- and both should fail +-- We don't kill the fetch_intermediate_results query directly, because that +-- resulted in randomly failing tests on CI. The reason for that is that there +-- is a race condition, where killing the fetch_intermediate_results query +-- removes the data files before the fetch_intermediate_results query from the +-- other node can read them. In theory a similar race condition still exists +-- when killing the COPY, but CI doesn't hit that race condition in practice. -- TRUNCATE target_table; -SELECT citus.mitmproxy('conn.onQuery(query="fetch_intermediate_results").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="COPY").kill()'); mitmproxy --------------------------------------------------------------------- (1 row) INSERT INTO target_table SELECT * FROM source_table; -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +ERROR: connection not open +CONTEXT: while executing command on localhost:xxxxx +while executing command on localhost:xxxxx SELECT * FROM target_table ORDER BY a; a | b --------------------------------------------------------------------- (0 rows) -SELECT citus.mitmproxy('conn.onQuery(query="fetch_intermediate_results").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="COPY").kill()'); mitmproxy --------------------------------------------------------------------- (1 row) INSERT INTO target_table SELECT * FROM replicated_source_table; -ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +ERROR: connection not open +CONTEXT: while executing command on localhost:xxxxx +while executing command on localhost:xxxxx SELECT * FROM target_table ORDER BY a; a | b --------------------------------------------------------------------- diff --git a/src/test/regress/expected/failure_multi_dml.out b/src/test/regress/expected/failure_multi_dml.out index eb336894f..7ca8a8f91 100644 --- a/src/test/regress/expected/failure_multi_dml.out +++ b/src/test/regress/expected/failure_multi_dml.out @@ -378,6 +378,7 @@ SELECT citus.mitmproxy('conn.onQuery(query="^COMMIT").kill()'); (1 row) +SET client_min_messages TO ERROR; BEGIN; DELETE FROM dml_test WHERE id = 1; DELETE FROM dml_test WHERE id = 2; @@ -385,11 +386,7 @@ INSERT INTO dml_test VALUES (5, 'Epsilon'); UPDATE dml_test SET name = 'alpha' WHERE id = 1; UPDATE dml_test SET name = 'gamma' WHERE id = 3; COMMIT; -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: failed to commit transaction on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx +RESET client_min_messages; -- all changes should be committed because we injected -- the failure on the COMMIT time. And, we should not -- mark any placements as INVALID diff --git a/src/test/regress/expected/failure_online_move_shard_placement.out b/src/test/regress/expected/failure_online_move_shard_placement.out index 777a6e59e..0ea70a2eb 100644 --- a/src/test/regress/expected/failure_online_move_shard_placement.out +++ b/src/test/regress/expected/failure_online_move_shard_placement.out @@ -110,14 +110,10 @@ SELECT master_move_shard_placement(101, 'localhost', :worker_1_port, 'localhost' ERROR: connection not open CONTEXT: while executing command on localhost:xxxxx -- failure when enabling the subscriptions -SELECT citus.mitmproxy('conn.onQuery(query="^ALTER SUBSCRIPTION .* ENABLE").cancel(' || :pid || ')'); - mitmproxy ---------------------------------------------------------------------- - -(1 row) - -SELECT master_move_shard_placement(101, 'localhost', :worker_1_port, 'localhost', :worker_2_proxy_port); -ERROR: canceling statement due to user request +-- This test can be enabled again once this postgres bug is fixed: +-- https://www.postgresql.org/message-id/flat/HE1PR8303MB0075BF78AF1BE904050DA16BF7729%40HE1PR8303MB0075.EURPRD83.prod.outlook.com +-- SELECT citus.mitmproxy('conn.onQuery(query="^ALTER SUBSCRIPTION .* ENABLE").cancel(' || :pid || ')'); +-- SELECT master_move_shard_placement(101, 'localhost', :worker_1_port, 'localhost', :worker_2_proxy_port); -- failure on polling subscription state SELECT citus.mitmproxy('conn.onQuery(query="^SELECT count\(\*\) FROM pg_subscription_rel").kill()'); mitmproxy diff --git a/src/test/regress/expected/failure_parallel_connection.out b/src/test/regress/expected/failure_parallel_connection.out index 37321b1b3..37ee346f5 100644 --- a/src/test/regress/expected/failure_parallel_connection.out +++ b/src/test/regress/expected/failure_parallel_connection.out @@ -47,7 +47,7 @@ BEGIN; 0 (1 row) - SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).kill()'); + SELECT citus.mitmproxy('conn.onQuery(query="^SELECT count").after(1).kill()'); mitmproxy --------------------------------------------------------------------- diff --git a/src/test/regress/expected/failure_savepoints.out b/src/test/regress/expected/failure_savepoints.out index 697b5b190..9b155e90e 100644 --- a/src/test/regress/expected/failure_savepoints.out +++ b/src/test/regress/expected/failure_savepoints.out @@ -10,6 +10,7 @@ SELECT citus.mitmproxy('conn.allow()'); SET citus.shard_count = 2; SET citus.shard_replication_factor = 1; -- one shard per worker SET citus.next_shard_id TO 100950; +SET client_min_messages TO ERROR; ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART 150; CREATE TABLE artists ( id bigint NOT NULL, @@ -36,13 +37,6 @@ SELECT citus.mitmproxy('conn.onQuery(query="^SAVEPOINT").kill()'); BEGIN; INSERT INTO artists VALUES (5, 'Asher Lev'); SAVEPOINT s1; -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection to the remote node localhost:xxxxx failed with the following error: connection not open -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx ERROR: connection not open CONTEXT: while executing command on localhost:xxxxx DELETE FROM artists WHERE id=4; @@ -68,16 +62,6 @@ UPDATE artists SET name='a'; SAVEPOINT s1; DELETE FROM artists WHERE id=4; RELEASE SAVEPOINT s1; -WARNING: AbortSubTransaction while in COMMIT state -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection to the remote node localhost:xxxxx failed with the following error: connection not open -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: savepoint "savepoint_2" does not exist -CONTEXT: while executing command on localhost:xxxxx ERROR: connection not open CONTEXT: while executing command on localhost:xxxxx ROLLBACK; @@ -99,10 +83,6 @@ INSERT INTO artists VALUES (5, 'Asher Lev'); SAVEPOINT s1; DELETE FROM artists WHERE id=4; ROLLBACK TO SAVEPOINT s1; -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx COMMIT; ERROR: failure on connection marked as essential: localhost:xxxxx SELECT * FROM artists WHERE id IN (4, 5); @@ -125,14 +105,6 @@ RELEASE SAVEPOINT s1; SAVEPOINT s2; INSERT INTO artists VALUES (5, 'Jacob Kahn'); RELEASE SAVEPOINT s2; -WARNING: AbortSubTransaction while in COMMIT state -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection to the remote node localhost:xxxxx failed with the following error: connection not open -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx ERROR: connection not open CONTEXT: while executing command on localhost:xxxxx COMMIT; @@ -156,10 +128,6 @@ ROLLBACK TO SAVEPOINT s1; SAVEPOINT s2; DELETE FROM artists WHERE id=5; ROLLBACK TO SAVEPOINT s2; -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx COMMIT; ERROR: failure on connection marked as essential: localhost:xxxxx SELECT * FROM artists WHERE id IN (4, 5); @@ -205,12 +173,6 @@ INSERT INTO artists VALUES (6, 'John J. Audubon'); INSERT INTO artists VALUES (7, 'Emily Carr'); INSERT INTO artists VALUES (7, 'Emily Carr'); ROLLBACK TO SAVEPOINT s1; -WARNING: connection not open -WARNING: connection not open -WARNING: connection not open -WARNING: connection to the remote node localhost:xxxxx failed with the following error: connection not open -WARNING: connection not open -WARNING: connection not open COMMIT; ERROR: failure on connection marked as essential: localhost:xxxxx SELECT * FROM artists WHERE id=6; @@ -242,10 +204,6 @@ SELECT citus.mitmproxy('conn.onQuery(query="^SAVEPOINT").kill()'); BEGIN; INSERT INTO researchers VALUES (7, 4, 'Jan Plaza'); SAVEPOINT s1; -WARNING: connection not open -WARNING: connection to the remote node localhost:xxxxx failed with the following error: connection not open -WARNING: connection not open -WARNING: connection not open ERROR: connection not open INSERT INTO researchers VALUES (8, 4, 'Alonzo Church'); ERROR: current transaction is aborted, commands ignored until end of transaction block @@ -281,8 +239,6 @@ INSERT INTO researchers VALUES (7, 4, 'Jan Plaza'); SAVEPOINT s1; INSERT INTO researchers VALUES (8, 4, 'Alonzo Church'); ROLLBACK TO s1; -WARNING: connection not open -WARNING: connection not open RELEASE SAVEPOINT s1; COMMIT; ERROR: failure on connection marked as essential: localhost:xxxxx @@ -314,12 +270,6 @@ SAVEPOINT s1; INSERT INTO researchers VALUES (8, 4, 'Alonzo Church'); ROLLBACK TO s1; RELEASE SAVEPOINT s1; -WARNING: AbortSubTransaction while in COMMIT state -WARNING: connection not open -WARNING: connection to the remote node localhost:xxxxx failed with the following error: connection not open -WARNING: connection not open -WARNING: connection not open -WARNING: savepoint "savepoint_3" does not exist ERROR: connection not open COMMIT; -- should see correct results from healthy placement and one bad placement @@ -361,16 +311,11 @@ SELECT * FROM ref; (1 row) ROLLBACK TO SAVEPOINT start; -WARNING: connection not open -WARNING: connection not open SELECT * FROM ref; -WARNING: connection not open -WARNING: connection to the remote node localhost:xxxxx failed with the following error: connection not open -WARNING: connection not open -WARNING: connection not open ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open END; -- clean up +RESET client_min_messages; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- diff --git a/src/test/regress/expected/failure_single_select.out b/src/test/regress/expected/failure_single_select.out index 723f5fadc..5d17cc4ad 100644 --- a/src/test/regress/expected/failure_single_select.out +++ b/src/test/regress/expected/failure_single_select.out @@ -23,7 +23,7 @@ SELECT create_distributed_table('select_test', 'key'); -- put data in shard for which mitm node is first placement INSERT INTO select_test VALUES (3, 'test data'); -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").kill()'); mitmproxy --------------------------------------------------------------------- @@ -45,7 +45,7 @@ WARNING: connection to the remote node localhost:xxxxx failed with the followin -- kill after first SELECT; txn should fail as INSERT triggers -- 2PC (and placementis not marked bad) -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").kill()'); mitmproxy --------------------------------------------------------------------- @@ -66,7 +66,7 @@ TRUNCATE select_test; -- now the same tests with query cancellation -- put data in shard for which mitm node is first placement INSERT INTO select_test VALUES (3, 'test data'); -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").cancel(' || pg_backend_pid() || ')'); mitmproxy --------------------------------------------------------------------- @@ -77,7 +77,7 @@ ERROR: canceling statement due to user request SELECT * FROM select_test WHERE key = 3; ERROR: canceling statement due to user request -- cancel after first SELECT; txn should fail and nothing should be marked as invalid -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").cancel(' || pg_backend_pid() || ')'); mitmproxy --------------------------------------------------------------------- @@ -107,7 +107,7 @@ SELECT citus.mitmproxy('conn.allow()'); TRUNCATE select_test; -- cancel the second query -- error after second SELECT; txn should fail -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).cancel(' || pg_backend_pid() || ')'); mitmproxy --------------------------------------------------------------------- @@ -126,7 +126,7 @@ SELECT * FROM select_test WHERE key = 3; ERROR: canceling statement due to user request COMMIT; -- error after second SELECT; txn should fails the transaction -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).reset()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).reset()'); mitmproxy --------------------------------------------------------------------- @@ -144,7 +144,7 @@ INSERT INTO select_test VALUES (3, 'even more data'); SELECT * FROM select_test WHERE key = 3; ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open COMMIT; -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(2).kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*pg_prepared_xacts").after(2).kill()'); mitmproxy --------------------------------------------------------------------- @@ -173,7 +173,7 @@ SELECT create_distributed_table('select_test', 'key'); SET citus.max_cached_conns_per_worker TO 1; -- allow connection to be cached INSERT INTO select_test VALUES (1, 'test data'); -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).kill()'); mitmproxy --------------------------------------------------------------------- @@ -188,7 +188,7 @@ SELECT * FROM select_test WHERE key = 1; SELECT * FROM select_test WHERE key = 1; ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open -- now the same test with query cancellation -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).cancel(' || pg_backend_pid() || ')'); mitmproxy --------------------------------------------------------------------- diff --git a/src/test/regress/expected/failure_test_helpers.out b/src/test/regress/expected/failure_test_helpers.out index 3e64b1f83..8c2be9825 100644 --- a/src/test/regress/expected/failure_test_helpers.out +++ b/src/test/regress/expected/failure_test_helpers.out @@ -10,10 +10,6 @@ SELECT pg_reload_conf(); t (1 row) -CREATE OR REPLACE FUNCTION wait_until_metadata_sync(timeout INTEGER DEFAULT 15000) - RETURNS void - LANGUAGE C STRICT - AS 'citus'; -- Add some helper functions for sending commands to mitmproxy CREATE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ DECLARE diff --git a/src/test/regress/expected/failure_truncate.out b/src/test/regress/expected/failure_truncate.out index b0dda6bda..4e332252e 100644 --- a/src/test/regress/expected/failure_truncate.out +++ b/src/test/regress/expected/failure_truncate.out @@ -266,7 +266,7 @@ SELECT count(*) FROM test_table; -- refill the table TRUNCATE test_table; INSERT INTO test_table SELECT x,x FROM generate_series(1,20) as f(x); -SET client_min_messages TO WARNING; +SET client_min_messages TO ERROR; -- now kill just after the worker sends response to -- COMMIT command, so we'll have lots of warnings but the command -- should have been committed both on the distributed table and the placements @@ -277,16 +277,6 @@ SELECT citus.mitmproxy('conn.onCommandComplete(command="^COMMIT").kill()'); (1 row) TRUNCATE test_table; -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: failed to commit transaction on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: failed to commit transaction on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- @@ -305,7 +295,6 @@ SELECT count(*) FROM test_table; 0 (1 row) -SET client_min_messages TO ERROR; INSERT INTO test_table SELECT x,x FROM generate_series(1,20) as f(x); -- now cancel just after the worker sends response to -- but Postgres doesn't accept interrupts during COMMIT and ROLLBACK diff --git a/src/test/regress/expected/failure_vacuum.out b/src/test/regress/expected/failure_vacuum.out index 632562b52..617d40d3a 100644 --- a/src/test/regress/expected/failure_vacuum.out +++ b/src/test/regress/expected/failure_vacuum.out @@ -39,6 +39,7 @@ SELECT citus.mitmproxy('conn.onQuery(query="^ANALYZE").kill()'); ANALYZE vacuum_test; ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open +SET client_min_messages TO ERROR; SELECT citus.mitmproxy('conn.onQuery(query="^COMMIT").kill()'); mitmproxy --------------------------------------------------------------------- @@ -46,11 +47,7 @@ SELECT citus.mitmproxy('conn.onQuery(query="^COMMIT").kill()'); (1 row) ANALYZE vacuum_test; -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx -WARNING: failed to commit transaction on localhost:xxxxx -WARNING: connection not open -CONTEXT: while executing command on localhost:xxxxx +RESET client_min_messages; SELECT citus.mitmproxy('conn.allow()'); mitmproxy --------------------------------------------------------------------- diff --git a/src/test/regress/expected/function_propagation.out b/src/test/regress/expected/function_propagation.out index 5c61761fb..2787e7daf 100644 --- a/src/test/regress/expected/function_propagation.out +++ b/src/test/regress/expected/function_propagation.out @@ -1,5 +1,6 @@ CREATE SCHEMA function_propagation_schema; SET search_path TO 'function_propagation_schema'; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 10000; -- Check whether supported dependencies can be distributed while propagating functions -- Check types SET citus.enable_metadata_sync TO OFF; @@ -1115,7 +1116,7 @@ SELECT create_distributed_function('func_to_colocate(int)', colocate_with:='tbl_ 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 | + | 10002 | (1 row) -- convert to non-delegated @@ -1143,7 +1144,7 @@ SELECT create_distributed_function('func_to_colocate(int)','$1','tbl_to_colocate 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 --------------------------------------------------------------------- - 0 | 10005 | + 0 | 10001 | (1 row) -- try create or replace the same func @@ -1152,7 +1153,7 @@ CREATE OR REPLACE FUNCTION func_to_colocate (a int) returns int as $$select 1;$$ 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 --------------------------------------------------------------------- - 0 | 10005 | + 0 | 10001 | (1 row) -- convert to non-delegated @@ -1180,7 +1181,7 @@ SELECT create_distributed_function('func_to_colocate(int)','$1','tbl_to_colocate 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 --------------------------------------------------------------------- - 0 | 10005 | t + 0 | 10001 | t (1 row) -- convert to non-delegated diff --git a/src/test/regress/expected/global_cancel.out b/src/test/regress/expected/global_cancel.out index a028df420..7ca531820 100644 --- a/src/test/regress/expected/global_cancel.out +++ b/src/test/regress/expected/global_cancel.out @@ -64,7 +64,7 @@ ERROR: canceling statement due to user request SET client_min_messages TO DEBUG; -- 10000000000 is the node id multiplier for global pid SELECT pg_cancel_backend(10000000000 * citus_coordinator_nodeid() + 0); -DEBUG: PID 0 is not a PostgreSQL server process +DEBUG: PID 0 is not a PostgreSQL backend process DETAIL: from localhost:xxxxx pg_cancel_backend --------------------------------------------------------------------- @@ -72,7 +72,7 @@ DETAIL: from localhost:xxxxx (1 row) SELECT pg_terminate_backend(10000000000 * citus_coordinator_nodeid() + 0); -DEBUG: PID 0 is not a PostgreSQL server process +DEBUG: PID 0 is not a PostgreSQL backend process DETAIL: from localhost:xxxxx pg_terminate_backend --------------------------------------------------------------------- diff --git a/src/test/regress/expected/grant_on_schema_propagation.out b/src/test/regress/expected/grant_on_schema_propagation.out index 795420a9d..410865d49 100644 --- a/src/test/regress/expected/grant_on_schema_propagation.out +++ b/src/test/regress/expected/grant_on_schema_propagation.out @@ -1,6 +1,16 @@ -- -- GRANT_ON_SCHEMA_PROPAGATION -- +-- this test has different output for PG13/14 compared to PG15 +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + -- test grants are propagated when the schema is CREATE SCHEMA dist_schema; CREATE TABLE dist_schema.dist_table (id int); @@ -332,16 +342,16 @@ GRANT USAGE ON SCHEMA PUBLIC TO PUBLIC; RESET ROLE; -- check if the grants are propagated correctly SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; - nspname | nspacl + nspname | nspacl --------------------------------------------------------------------- - public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} + public | {pg_database_owner=UC/pg_database_owner,=UC/pg_database_owner,role_1=U*C*/pg_database_owner,=U/role_1} (1 row) \c - - - :worker_1_port SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; - nspname | nspacl + nspname | nspacl --------------------------------------------------------------------- - public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} + public | {pg_database_owner=UC/pg_database_owner,=UC/pg_database_owner,role_1=U*C*/pg_database_owner,=U/role_1} (1 row) \c - - - :master_port @@ -354,16 +364,16 @@ SELECT 1 FROM master_add_node('localhost', :worker_2_port); -- check if the grants are propagated correctly SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; - nspname | nspacl + nspname | nspacl --------------------------------------------------------------------- - public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} + public | {pg_database_owner=UC/pg_database_owner,=UC/pg_database_owner,role_1=U*C*/pg_database_owner,=U/role_1} (1 row) \c - - - :worker_2_port SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; - nspname | nspacl + nspname | nspacl --------------------------------------------------------------------- - public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} + public | {pg_database_owner=UC/pg_database_owner,=UC/pg_database_owner,role_1=U*C*/pg_database_owner,=U/role_1} (1 row) \c - - - :master_port @@ -371,16 +381,16 @@ SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspna REVOKE CREATE, USAGE ON SCHEMA PUBLIC FROM role_1 CASCADE; -- check if the grants are propagated correctly SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; - nspname | nspacl + nspname | nspacl --------------------------------------------------------------------- - public | {postgres=UC/postgres,=UC/postgres} + public | {pg_database_owner=UC/pg_database_owner,=UC/pg_database_owner} (1 row) \c - - - :worker_1_port SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; - nspname | nspacl + nspname | nspacl --------------------------------------------------------------------- - public | {postgres=UC/postgres,=UC/postgres} + public | {pg_database_owner=UC/pg_database_owner,=UC/pg_database_owner} (1 row) \c - - - :master_port diff --git a/src/test/regress/expected/grant_on_schema_propagation_0.out b/src/test/regress/expected/grant_on_schema_propagation_0.out new file mode 100644 index 000000000..6b8b782ca --- /dev/null +++ b/src/test/regress/expected/grant_on_schema_propagation_0.out @@ -0,0 +1,398 @@ +-- +-- GRANT_ON_SCHEMA_PROPAGATION +-- +-- this test has different output for PG13/14 compared to PG15 +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +-- test grants are propagated when the schema is +CREATE SCHEMA dist_schema; +CREATE TABLE dist_schema.dist_table (id int); +CREATE SCHEMA another_dist_schema; +CREATE TABLE another_dist_schema.dist_table (id int); +SET citus.enable_ddl_propagation TO off; +CREATE SCHEMA non_dist_schema; +SET citus.enable_ddl_propagation TO on; +-- create roles on all nodes +CREATE USER role_1; +CREATE USER role_2; +CREATE USER role_3; +-- do some varying grants +GRANT USAGE, CREATE ON SCHEMA dist_schema TO role_1 WITH GRANT OPTION; +GRANT USAGE ON SCHEMA dist_schema TO role_2; +SET ROLE role_1; +GRANT USAGE ON SCHEMA dist_schema TO role_3 WITH GRANT OPTION; +GRANT CREATE ON SCHEMA dist_schema TO role_3; +GRANT CREATE, USAGE ON SCHEMA dist_schema TO PUBLIC; +RESET ROLE; +GRANT USAGE ON SCHEMA dist_schema TO PUBLIC; +SELECT create_distributed_table('dist_schema.dist_table', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('another_dist_schema.dist_table', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'dist_schema'; + nspname | nspacl +--------------------------------------------------------------------- + dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U/postgres,role_3=U*C/role_1,=UC/role_1,=U/postgres} +(1 row) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'dist_schema'; + nspname | nspacl +--------------------------------------------------------------------- + dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U/postgres,role_3=U*C/role_1,=UC/role_1,=U/postgres} +(1 row) + +\c - - - :master_port +-- grant all permissions +GRANT ALL ON SCHEMA dist_schema, another_dist_schema, non_dist_schema TO role_1, role_2, role_3 WITH GRANT OPTION; +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U*C*/postgres,role_3=U*C*/postgres} + dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U*C*/postgres,role_3=U*C/role_1,=UC/role_1,=U/postgres,role_3=U*C*/postgres} + non_dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U*C*/postgres,role_3=U*C*/postgres} +(3 rows) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U*C*/postgres,role_3=U*C*/postgres} + dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U*C*/postgres,role_3=U*C/role_1,=UC/role_1,=U/postgres,role_3=U*C*/postgres} +(2 rows) + +\c - - - :master_port +-- revoke all permissions +REVOKE ALL ON SCHEMA dist_schema, another_dist_schema, non_dist_schema FROM role_1, role_2, role_3, PUBLIC CASCADE; +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres} + dist_schema | {postgres=UC/postgres} + non_dist_schema | {postgres=UC/postgres} +(3 rows) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres} + dist_schema | {postgres=UC/postgres} +(2 rows) + +\c - - - :master_port +-- grant with multiple permissions, roles and schemas +GRANT USAGE, CREATE ON SCHEMA dist_schema, another_dist_schema, non_dist_schema TO role_1, role_2, role_3; +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_1=UC/postgres,role_2=UC/postgres,role_3=UC/postgres} + dist_schema | {postgres=UC/postgres,role_1=UC/postgres,role_2=UC/postgres,role_3=UC/postgres} + non_dist_schema | {postgres=UC/postgres,role_1=UC/postgres,role_2=UC/postgres,role_3=UC/postgres} +(3 rows) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_1=UC/postgres,role_2=UC/postgres,role_3=UC/postgres} + dist_schema | {postgres=UC/postgres,role_1=UC/postgres,role_2=UC/postgres,role_3=UC/postgres} +(2 rows) + +\c - - - :master_port +-- revoke with multiple permissions, roles and schemas +REVOKE USAGE, CREATE ON SCHEMA dist_schema, another_dist_schema, non_dist_schema FROM role_1, role_2; +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_3=UC/postgres} + dist_schema | {postgres=UC/postgres,role_3=UC/postgres} + non_dist_schema | {postgres=UC/postgres,role_3=UC/postgres} +(3 rows) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_3=UC/postgres} + dist_schema | {postgres=UC/postgres,role_3=UC/postgres} +(2 rows) + +\c - - - :master_port +-- grant with grant option +GRANT USAGE ON SCHEMA dist_schema TO role_1, role_3 WITH GRANT OPTION; +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_3=UC/postgres} + dist_schema | {postgres=UC/postgres,role_3=U*C/postgres,role_1=U*/postgres} +(2 rows) + +\c - - - :master_port +-- revoke grant option for +REVOKE GRANT OPTION FOR USAGE ON SCHEMA dist_schema FROM role_3; +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_3=UC/postgres} + dist_schema | {postgres=UC/postgres,role_3=UC/postgres,role_1=U*/postgres} +(2 rows) + +\c - - - :master_port +-- test current_user +SET citus.enable_alter_role_propagation TO ON; +ALTER ROLE role_1 SUPERUSER; +SET citus.enable_alter_role_propagation TO OFF; +SET ROLE role_1; +-- this is only supported on citus enterprise where multiple users can be managed +-- The output of the nspname select below will indicate if the create has been granted +GRANT CREATE ON SCHEMA dist_schema TO CURRENT_USER; +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname IN ('dist_schema', 'another_dist_schema', 'non_dist_schema') ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + another_dist_schema | {postgres=UC/postgres,role_3=UC/postgres} + dist_schema | {postgres=UC/postgres,role_3=UC/postgres,role_1=U*C/postgres} +(2 rows) + +\c - - - :master_port +RESET ROLE; +SET citus.enable_alter_role_propagation TO ON; +ALTER ROLE role_1 NOSUPERUSER; +SET citus.enable_alter_role_propagation TO OFF; +DROP TABLE dist_schema.dist_table, another_dist_schema.dist_table; +DROP SCHEMA dist_schema; +DROP SCHEMA another_dist_schema; +DROP SCHEMA non_dist_schema; +-- test if the grantors are propagated correctly +-- first remove one of the worker nodes +SET citus.shard_replication_factor TO 1; +SELECT master_remove_node('localhost', :worker_2_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + +-- create a new schema +CREATE SCHEMA grantor_schema; +-- give cascading permissions +GRANT USAGE, CREATE ON SCHEMA grantor_schema TO role_1 WITH GRANT OPTION; +GRANT CREATE ON SCHEMA grantor_schema TO PUBLIC; +SET ROLE role_1; +GRANT USAGE ON SCHEMA grantor_schema TO role_2 WITH GRANT OPTION; +GRANT CREATE ON SCHEMA grantor_schema TO role_2; +GRANT USAGE, CREATE ON SCHEMA grantor_schema TO role_3; +GRANT CREATE, USAGE ON SCHEMA grantor_schema TO PUBLIC; +SET ROLE role_2; +GRANT USAGE ON SCHEMA grantor_schema TO role_3; +RESET ROLE; +-- distribute the schema +CREATE TABLE grantor_schema.grantor_table (id INT); +SELECT create_distributed_table('grantor_schema.grantor_table', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- check if the grantors are propagated correctly +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=U*C*/postgres,=C/postgres,role_2=U*C/role_1,role_3=UC/role_1,=UC/role_1,role_3=U/role_2} +(1 row) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=U*C*/postgres,=C/postgres,role_2=U*C/role_1,role_3=UC/role_1,=UC/role_1,role_3=U/role_2} +(1 row) + +\c - - - :master_port +-- add the previously removed node +SELECT 1 FROM master_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- check if the grantors are propagated correctly +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=U*C*/postgres,=C/postgres,role_2=U*C/role_1,role_3=UC/role_1,=UC/role_1,role_3=U/role_2} +(1 row) + +\c - - - :worker_2_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=U*C*/postgres,=C/postgres,role_2=U*C/role_1,role_3=UC/role_1,=UC/role_1,role_3=U/role_2} +(1 row) + +\c - - - :master_port +-- revoke one of the permissions +REVOKE USAGE ON SCHEMA grantor_schema FROM role_1 CASCADE; +-- check if revoke worked correctly +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=C*/postgres,=C/postgres,role_2=C/role_1,role_3=C/role_1,=C/role_1} +(1 row) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=C*/postgres,=C/postgres,role_2=C/role_1,role_3=C/role_1,=C/role_1} +(1 row) + +\c - - - :master_port +-- test if grantor propagates correctly on already distributed schemas +GRANT USAGE ON SCHEMA grantor_schema TO role_1 WITH GRANT OPTION; +SET ROLE role_1; +GRANT USAGE ON SCHEMA grantor_schema TO role_2; +GRANT USAGE ON SCHEMA grantor_schema TO role_3 WITH GRANT OPTION; +SET ROLE role_3; +GRANT USAGE ON SCHEMA grantor_schema TO role_2; +RESET ROLE; +-- check the results +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=U*C*/postgres,=C/postgres,role_2=UC/role_1,role_3=U*C/role_1,=C/role_1,role_2=U/role_3} +(1 row) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'grantor_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + grantor_schema | {postgres=UC/postgres,role_1=U*C*/postgres,=C/postgres,role_2=UC/role_1,role_3=U*C/role_1,=C/role_1,role_2=U/role_3} +(1 row) + +\c - - - :master_port +DROP TABLE grantor_schema.grantor_table; +DROP SCHEMA grantor_schema CASCADE; +-- test distributing the schema with another user +CREATE SCHEMA dist_schema; +GRANT ALL ON SCHEMA dist_schema TO role_1 WITH GRANT OPTION; +SET ROLE role_1; +GRANT ALL ON SCHEMA dist_schema TO role_2 WITH GRANT OPTION; +CREATE TABLE dist_schema.dist_table (id int); +SELECT create_distributed_table('dist_schema.dist_table', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'dist_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U*C*/role_1} +(1 row) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'dist_schema' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + dist_schema | {postgres=UC/postgres,role_1=U*C*/postgres,role_2=U*C*/role_1} +(1 row) + +\c - - - :master_port +DROP TABLE dist_schema.dist_table; +DROP SCHEMA dist_schema CASCADE; +-- test grants on public schema +-- first remove one of the worker nodes +SET citus.shard_replication_factor TO 1; +SELECT master_remove_node('localhost', :worker_2_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + +-- 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'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- give cascading permissions +GRANT USAGE, CREATE ON SCHEMA PUBLIC TO role_1 WITH GRANT OPTION; +SET ROLE role_1; +GRANT USAGE ON SCHEMA PUBLIC TO PUBLIC; +RESET ROLE; +-- check if the grants are propagated correctly +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} +(1 row) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} +(1 row) + +\c - - - :master_port +-- add the previously removed node +SELECT 1 FROM master_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- check if the grants are propagated correctly +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} +(1 row) + +\c - - - :worker_2_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + public | {postgres=UC/postgres,=UC/postgres,role_1=U*C*/postgres,=U/role_1} +(1 row) + +\c - - - :master_port +-- revoke those new permissions +REVOKE CREATE, USAGE ON SCHEMA PUBLIC FROM role_1 CASCADE; +-- check if the grants are propagated correctly +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + public | {postgres=UC/postgres,=UC/postgres} +(1 row) + +\c - - - :worker_1_port +SELECT nspname, nspacl FROM pg_namespace WHERE nspname = 'public' ORDER BY nspname; + nspname | nspacl +--------------------------------------------------------------------- + public | {postgres=UC/postgres,=UC/postgres} +(1 row) + +\c - - - :master_port +DROP TABLE public_schema_table; +DROP ROLE role_1, role_2, role_3; diff --git a/src/test/regress/expected/insert_select_repartition.out b/src/test/regress/expected/insert_select_repartition.out index f6e4f17a5..913419072 100644 --- a/src/test/regress/expected/insert_select_repartition.out +++ b/src/test/regress/expected/insert_select_repartition.out @@ -1,3 +1,17 @@ +-- +-- INSERT_SELECT_REPARTITION +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + -- tests behaviour of INSERT INTO ... SELECT with repartitioning CREATE SCHEMA insert_select_repartition; SET search_path TO 'insert_select_repartition'; @@ -29,10 +43,10 @@ HINT: Ensure the target table's partition column has a corresponding simple col 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' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213585 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213583_to_0,repartitioned_results_xxxxx_from_4213584_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213586 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213582_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213587 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_2,repartitioned_results_xxxxx_from_4213582_to_2,repartitioned_results_xxxxx_from_4213584_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213588 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213585 AS citus_table_alias (a) SELECT intermediate_result.a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213583_to_0,repartitioned_results_xxxxx_from_4213584_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213586 AS citus_table_alias (a) SELECT intermediate_result.a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213582_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213587 AS citus_table_alias (a) SELECT intermediate_result.a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_2,repartitioned_results_xxxxx_from_4213582_to_2,repartitioned_results_xxxxx_from_4213584_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213588 AS citus_table_alias (a) SELECT intermediate_result.a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) RESET client_min_messages; SELECT * FROM target_table WHERE a=-1 OR a=-3 OR a=-7 ORDER BY a; a @@ -79,8 +93,8 @@ DETAIL: The target table's partition column should correspond to a partition co DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 2 with name 'key' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, value, key) SELECT intermediate_result.f1, intermediate_result.value, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, value, key) SELECT intermediate_result.f1, intermediate_result.value, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) RESET client_min_messages; SELECT * FROM target_table ORDER BY key; f1 | value | key @@ -109,8 +123,8 @@ DETAIL: The target table's partition column should correspond to a partition co DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 2 with name 'key' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, value, key) SELECT intermediate_result.f1, intermediate_result.value, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, value, key) SELECT intermediate_result.f1, intermediate_result.value, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) RESET client_min_messages; SELECT * FROM target_table ORDER BY key; f1 | value | key @@ -133,8 +147,8 @@ DETAIL: The target table's partition column should correspond to a partition co DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 1 with name 'key' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, key) SELECT intermediate_result.f1, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, key) SELECT intermediate_result.f1, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) RESET client_min_messages; SELECT * FROM target_table ORDER BY key; f1 | value | key @@ -159,8 +173,8 @@ DETAIL: The target table's partition column should correspond to a partition co DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 1 with name 'key' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) ON CONFLICT(key) DO UPDATE SET f1 = 1 -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) ON CONFLICT(key) DO UPDATE SET f1 = 1 +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, key) SELECT intermediate_result.f1, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) ON CONFLICT(key) DO UPDATE SET f1 = 1 +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, key) SELECT intermediate_result.f1, intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) ON CONFLICT(key) DO UPDATE SET f1 = 1 RESET client_min_messages; SELECT * FROM target_table ORDER BY key; f1 | value | key @@ -209,8 +223,8 @@ DETAIL: The data type of the target table's partition column should exactly mat DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: partitioning SELECT query by column index 0 with name 'col_1' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213593 AS citus_table_alias (col_1, col_2) SELECT col_1, col_2 FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213597_to_0,repartitioned_results_xxxxx_from_4213600_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer) ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213594 AS citus_table_alias (col_1, col_2) SELECT col_1, col_2 FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213599_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer) ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213593 AS citus_table_alias (col_1, col_2) SELECT intermediate_result.col_1, intermediate_result.col_2 FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213597_to_0,repartitioned_results_xxxxx_from_4213600_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer) ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213594 AS citus_table_alias (col_1, col_2) SELECT intermediate_result.col_1, intermediate_result.col_2 FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213599_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer) ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 RESET client_min_messages; SELECT * FROM target_table ORDER BY 1; col_1 | col_2 @@ -474,7 +488,7 @@ WITH c AS ( SELECT mapped_key, c FROM source_table RETURNING *) SELECT * FROM c ORDER by a; -DEBUG: generating subplan XXX_1 for CTE c: INSERT INTO insert_select_repartition.target_table (a, b) SELECT mapped_key, c FROM insert_select_repartition.source_table RETURNING target_table.a, target_table.b +DEBUG: generating subplan XXX_1 for CTE c: INSERT INTO insert_select_repartition.target_table (a, b) SELECT source_table.mapped_key, source_table.c FROM insert_select_repartition.source_table RETURNING target_table.a, target_table.b DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match 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_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer[])) c ORDER BY a DEBUG: performing repartitioned INSERT ... SELECT @@ -583,9 +597,9 @@ DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition 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' -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,repartitioned_results_xxxxx_from_4213609_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_4213606_to_2,repartitioned_results_xxxxx_from_4213607_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +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,repartitioned_results_xxxxx_from_4213609_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_4213606_to_2,repartitioned_results_xxxxx_from_4213607_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) RESET client_min_messages; SELECT * FROM target_table ORDER BY a; a | b @@ -702,23 +716,16 @@ PREPARE insert_plan AS INSERT INTO target_table SELECT a, max(b) FROM source_table WHERE a BETWEEN 1 AND 2 GROUP BY a; +SELECT public.coordinator_plan($Q$ EXPLAIN EXECUTE insert_plan; - QUERY PLAN +$Q$); + coordinator_plan --------------------------------------------------------------------- Custom Scan (Citus INSERT ... SELECT) (cost=0.00..0.00 rows=0 width=0) INSERT/SELECT method: repartition -> Custom Scan (Citus Adaptive) (cost=0.00..0.00 rows=100000 width=8) Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> GroupAggregate (cost=44.09..44.28 rows=11 width=8) - Group Key: a - -> Sort (cost=44.09..44.12 rows=11 width=8) - Sort Key: a - -> Seq Scan on source_table_4213606 source_table (cost=0.00..43.90 rows=11 width=8) - Filter: ((a >= 1) AND (a <= 2)) -(13 rows) +(4 rows) SET client_min_messages TO DEBUG1; EXECUTE insert_plan; @@ -755,7 +762,7 @@ WITH r AS ( 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: 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: 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: 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 @@ -763,13 +770,13 @@ DEBUG: Router planner cannot handle multi-shard select queries DEBUG: performing repartitioned INSERT ... SELECT DEBUG: performing repartitioned INSERT ... SELECT 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) RETURNING citus_table_alias.a, citus_table_alias.b -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) RETURNING citus_table_alias.a, citus_table_alias.b -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) RETURNING citus_table_alias.a, citus_table_alias.b +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) RETURNING citus_table_alias.a, citus_table_alias.b +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) RETURNING citus_table_alias.a, citus_table_alias.b +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) RETURNING citus_table_alias.a, citus_table_alias.b 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) +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 client_min_messages; SELECT * FROM target_table ORDER BY a, b; a | b @@ -899,7 +906,7 @@ DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition 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' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213617 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213613_to_0,repartitioned_results_xxxxx_from_4213614_to_0,repartitioned_results_xxxxx_from_4213615_to_0,repartitioned_results_xxxxx_from_4213616_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213617 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213613_to_0,repartitioned_results_xxxxx_from_4213614_to_0,repartitioned_results_xxxxx_from_4213615_to_0,repartitioned_results_xxxxx_from_4213616_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) RESET client_min_messages; SELECT * FROM target_table ORDER BY a, b; a | b @@ -1010,9 +1017,9 @@ DEBUG: INSERT target table and the source relation of the SELECT partition colu 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' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213633 AS citus_table_alias (a, b, c, d) SELECT a, b, c, d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213629_to_0,repartitioned_results_xxxxx_from_4213630_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213634 AS citus_table_alias (a, b, c, d) SELECT a, b, c, d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213630_to_1,repartitioned_results_xxxxx_from_4213631_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213635 AS citus_table_alias (a, b, c, d) SELECT a, b, c, d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213632_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213633 AS citus_table_alias (a, b, c, d) SELECT intermediate_result.a, intermediate_result.b, intermediate_result.c, intermediate_result.d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213629_to_0,repartitioned_results_xxxxx_from_4213630_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213634 AS citus_table_alias (a, b, c, d) SELECT intermediate_result.a, intermediate_result.b, intermediate_result.c, intermediate_result.d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213630_to_1,repartitioned_results_xxxxx_from_4213631_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213635 AS citus_table_alias (a, b, c, d) SELECT intermediate_result.a, intermediate_result.b, intermediate_result.c, intermediate_result.d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213632_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) RESET client_min_messages; SELECT count(*) FROM target_table; count @@ -1214,8 +1221,8 @@ DEBUG: INSERT target table and the source relation of the SELECT partition colu 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' -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213639 AS enriched (c1, c2, c3, c4, c5, c6, cardinality, sum) SELECT c1, c2, c3, c4, c5, c6, cardinality, sum FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213644_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(c1 integer, c2 integer, c3 timestamp without time zone, c4 integer, c5 integer, c6 integer[], cardinality integer, sum integer) ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = (enriched.cardinality OPERATOR(pg_catalog.+) excluded.cardinality), sum = (enriched.sum OPERATOR(pg_catalog.+) excluded.sum) -DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213641 AS enriched (c1, c2, c3, c4, c5, c6, cardinality, sum) SELECT c1, c2, c3, c4, c5, c6, cardinality, sum FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213645_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(c1 integer, c2 integer, c3 timestamp without time zone, c4 integer, c5 integer, c6 integer[], cardinality integer, sum integer) ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = (enriched.cardinality OPERATOR(pg_catalog.+) excluded.cardinality), sum = (enriched.sum OPERATOR(pg_catalog.+) excluded.sum) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213639 AS enriched (c1, c2, c3, c4, c5, c6, cardinality, sum) SELECT intermediate_result.c1, intermediate_result.c2, intermediate_result.c3, intermediate_result.c4, intermediate_result.c5, intermediate_result.c6, intermediate_result.cardinality, intermediate_result.sum FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213644_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(c1 integer, c2 integer, c3 timestamp without time zone, c4 integer, c5 integer, c6 integer[], cardinality integer, sum integer) ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = (enriched.cardinality OPERATOR(pg_catalog.+) excluded.cardinality), sum = (enriched.sum OPERATOR(pg_catalog.+) excluded.sum) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213641 AS enriched (c1, c2, c3, c4, c5, c6, cardinality, sum) SELECT intermediate_result.c1, intermediate_result.c2, intermediate_result.c3, intermediate_result.c4, intermediate_result.c5, intermediate_result.c6, intermediate_result.cardinality, intermediate_result.sum FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213645_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(c1 integer, c2 integer, c3 timestamp without time zone, c4 integer, c5 integer, c6 integer[], cardinality integer, sum integer) ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = (enriched.cardinality OPERATOR(pg_catalog.+) excluded.cardinality), sum = (enriched.sum OPERATOR(pg_catalog.+) excluded.sum) RESET client_min_messages; EXPLAIN (COSTS OFF) INSERT INTO target_table AS enriched(c1, c2, c3, c4, c5, c6, cardinality, sum) SELECT c1, c2, c3, c4, -1::float AS c5, @@ -1254,8 +1261,10 @@ NOTICE: copying the data has completed (1 row) +SELECT public.plan_without_result_lines($Q$ explain (costs off) insert into table_with_sequences select y, x from table_with_sequences; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator @@ -1280,8 +1289,10 @@ NOTICE: copying the data has completed (1 row) +SELECT public.plan_without_result_lines($Q$ explain (costs off) insert into table_with_user_sequences select y, x from table_with_user_sequences; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Custom Scan (Citus INSERT ... SELECT) INSERT/SELECT method: pull to coordinator diff --git a/src/test/regress/expected/insert_select_repartition_0.out b/src/test/regress/expected/insert_select_repartition_0.out new file mode 100644 index 000000000..31377ef16 --- /dev/null +++ b/src/test/regress/expected/insert_select_repartition_0.out @@ -0,0 +1,1309 @@ +-- +-- INSERT_SELECT_REPARTITION +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +-- tests behaviour of INSERT INTO ... SELECT with repartitioning +CREATE SCHEMA insert_select_repartition; +SET search_path TO 'insert_select_repartition'; +SET citus.next_shard_id TO 4213581; +SET citus.shard_replication_factor TO 1; +-- 4 shards, hash distributed. +-- Negate distribution column value. +SET citus.shard_count TO 4; +CREATE TABLE source_table(a int); +SELECT create_distributed_table('source_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table SELECT * FROM generate_series(1, 10); +CREATE TABLE target_table(a int); +SELECT create_distributed_table('target_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG2; +INSERT INTO target_table SELECT -a FROM source_table; +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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'a' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213585 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213583_to_0,repartitioned_results_xxxxx_from_4213584_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213586 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213582_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213587 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_2,repartitioned_results_xxxxx_from_4213582_to_2,repartitioned_results_xxxxx_from_4213584_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213588 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) +RESET client_min_messages; +SELECT * FROM target_table WHERE a=-1 OR a=-3 OR a=-7 ORDER BY a; + a +--------------------------------------------------------------------- + -7 + -3 + -1 +(3 rows) + +DROP TABLE source_table, target_table; +-- +-- range partitioning, composite distribution column +-- +CREATE TYPE composite_key_type AS (f1 int, f2 text); +-- source +CREATE TABLE source_table(f1 int, key composite_key_type, value int, mapped_key composite_key_type); +SELECT create_distributed_table('source_table', 'key', 'range'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CALL public.create_range_partitioned_shards('source_table', '{"(0,a)","(25,a)"}','{"(24,z)","(49,z)"}'); +INSERT INTO source_table VALUES (0, (0, 'a'), 1, (0, 'a')); +INSERT INTO source_table VALUES (1, (1, 'b'), 2, (26, 'b')); +INSERT INTO source_table VALUES (2, (2, 'c'), 3, (3, 'c')); +INSERT INTO source_table VALUES (3, (4, 'd'), 4, (27, 'd')); +INSERT INTO source_table VALUES (4, (30, 'e'), 5, (30, 'e')); +INSERT INTO source_table VALUES (5, (31, 'f'), 6, (31, 'f')); +INSERT INTO source_table VALUES (6, (32, 'g'), 50, (8, 'g')); +-- target +CREATE TABLE target_table(f1 int DEFAULT 0, value int, key composite_key_type PRIMARY KEY); +SELECT create_distributed_table('target_table', 'key', 'range'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CALL public.create_range_partitioned_shards('target_table', '{"(0,a)","(25,a)"}','{"(24,z)","(49,z)"}'); +SET client_min_messages TO DEBUG2; +INSERT INTO target_table SELECT f1, value, mapped_key FROM source_table; +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 cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 2 with name 'key' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +RESET client_min_messages; +SELECT * FROM target_table ORDER BY key; + f1 | value | key +--------------------------------------------------------------------- + 0 | 1 | (0,a) + 2 | 3 | (3,c) + 6 | 50 | (8,g) + 1 | 2 | (26,b) + 3 | 4 | (27,d) + 4 | 5 | (30,e) + 5 | 6 | (31,f) +(7 rows) + +SELECT * FROM target_table WHERE key = (26, 'b')::composite_key_type; + f1 | value | key +--------------------------------------------------------------------- + 1 | 2 | (26,b) +(1 row) + +-- with explicit column names +TRUNCATE target_table; +SET client_min_messages TO DEBUG2; +INSERT INTO target_table(value, key) SELECT value, mapped_key FROM source_table; +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 cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 2 with name 'key' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, value, key) SELECT f1, value, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, value integer, key insert_select_repartition.composite_key_type) +RESET client_min_messages; +SELECT * FROM target_table ORDER BY key; + f1 | value | key +--------------------------------------------------------------------- + 0 | 1 | (0,a) + 0 | 3 | (3,c) + 0 | 50 | (8,g) + 0 | 2 | (26,b) + 0 | 4 | (27,d) + 0 | 5 | (30,e) + 0 | 6 | (31,f) +(7 rows) + +-- missing value for a column +TRUNCATE target_table; +SET client_min_messages TO DEBUG2; +INSERT INTO target_table(key) SELECT mapped_key AS key_renamed FROM source_table; +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 cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 1 with name 'key' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0,repartitioned_results_xxxxx_from_4213590_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) +RESET client_min_messages; +SELECT * FROM target_table ORDER BY key; + f1 | value | key +--------------------------------------------------------------------- + 0 | | (0,a) + 0 | | (3,c) + 0 | | (8,g) + 0 | | (26,b) + 0 | | (27,d) + 0 | | (30,e) + 0 | | (31,f) +(7 rows) + +-- ON CONFLICT +SET client_min_messages TO DEBUG2; +INSERT INTO target_table(key) +SELECT mapped_key AS key_renamed FROM source_table +WHERE (mapped_key).f1 % 2 = 1 +ON CONFLICT (key) DO UPDATE SET f1=1; +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 cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 1 with name 'key' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213591 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_0}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) ON CONFLICT(key) DO UPDATE SET f1 = 1 +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213592 AS citus_table_alias (f1, key) SELECT f1, key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213589_to_1,repartitioned_results_xxxxx_from_4213590_to_1}'::text[], 'text'::citus_copy_format) intermediate_result(f1 integer, key insert_select_repartition.composite_key_type) ON CONFLICT(key) DO UPDATE SET f1 = 1 +RESET client_min_messages; +SELECT * FROM target_table ORDER BY key; + f1 | value | key +--------------------------------------------------------------------- + 0 | | (0,a) + 1 | | (3,c) + 0 | | (8,g) + 0 | | (26,b) + 1 | | (27,d) + 0 | | (30,e) + 1 | | (31,f) +(7 rows) + +-- missing value for distribution column +INSERT INTO target_table(value) SELECT value FROM source_table; +ERROR: the partition column of table insert_select_repartition.target_table should have a value +DROP TABLE source_table, target_table; +-- different column types +-- verifies that we add necessary casts, otherwise even shard routing won't +-- work correctly and we will see 2 values for the same primary key. +CREATE TABLE target_table(col_1 int primary key, col_2 int); +SELECT create_distributed_table('target_table','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO target_table VALUES (1,2), (2,3), (3,4), (4,5), (5,6); +CREATE TABLE source_table(col_1 numeric, col_2 numeric, col_3 numeric); +SELECT create_distributed_table('source_table','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table VALUES (1,1,1), (3,3,3), (5,5,5); +SET client_min_messages TO DEBUG2; +INSERT INTO target_table +SELECT + col_1, col_2 +FROM + source_table +ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The data type of the target table's partition column should exactly match the data type of the corresponding simple column reference in the subquery. +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'col_1' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213593 AS citus_table_alias (col_1, col_2) SELECT col_1, col_2 FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213597_to_0,repartitioned_results_xxxxx_from_4213600_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer) ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213594 AS citus_table_alias (col_1, col_2) SELECT col_1, col_2 FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213599_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer) ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 +RESET client_min_messages; +SELECT * FROM target_table ORDER BY 1; + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 1 + 2 | 3 + 3 | 3 + 4 | 5 + 5 | 5 +(5 rows) + +DROP TABLE source_table, target_table; +-- +-- array coercion +-- +SET citus.shard_count TO 3; +CREATE TABLE source_table(a int, mapped_key int, c float[]); +SELECT create_distributed_table('source_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table VALUES (1, -1, ARRAY[1.1, 2.2, 3.3]), (2, -2, ARRAY[4.5, 5.8]), + (3, -3, ARRAY[]::float[]), (4, -4, ARRAY[3.3]); +SET citus.shard_count TO 2; +CREATE TABLE target_table(a int, b int[]); +SELECT create_distributed_table('target_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG1; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +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: performing repartitioned INSERT ... SELECT +RESET client_min_messages; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +-- +-- worker queries can have more columns than necessary. ExpandWorkerTargetEntry() +-- might add additional columns to the target list. +-- +TRUNCATE target_table; +\set VERBOSITY TERSE +-- first verify that the SELECT query below fetches 3 projected columns from workers +SET citus.log_remote_commands TO true; SET client_min_messages TO DEBUG; + CREATE TABLE results AS SELECT max(-a), array_agg(mapped_key) FROM source_table GROUP BY a; +DEBUG: Router planner cannot handle multi-shard select queries +NOTICE: issuing SELECT max((OPERATOR(pg_catalog.-) a)) AS max, array_agg(mapped_key) AS array_agg, a AS worker_column_3 FROM insert_select_repartition.source_table_4213601 source_table WHERE true GROUP BY a +NOTICE: issuing SELECT max((OPERATOR(pg_catalog.-) a)) AS max, array_agg(mapped_key) AS array_agg, a AS worker_column_3 FROM insert_select_repartition.source_table_4213602 source_table WHERE true GROUP BY a +NOTICE: issuing SELECT max((OPERATOR(pg_catalog.-) a)) AS max, array_agg(mapped_key) AS array_agg, a AS worker_column_3 FROM insert_select_repartition.source_table_4213603 source_table WHERE true GROUP BY a +RESET citus.log_remote_commands; RESET client_min_messages; +DROP TABLE results; +-- now verify that we don't write the extra columns to the intermediate result files and +-- insertion to the target works fine. +SET client_min_messages TO DEBUG1; +INSERT INTO target_table SELECT max(-a), array_agg(mapped_key) FROM source_table GROUP BY a; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DEBUG: performing repartitioned INSERT ... SELECT +RESET client_min_messages; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {-4} + -3 | {-3} + -2 | {-2} + -1 | {-1} +(4 rows) + +-- +-- repartitioned INSERT/SELECT followed/preceded by other DML in same transaction +-- +-- case 1. followed by DELETE +TRUNCATE target_table; +BEGIN; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +DELETE FROM target_table; +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- +(0 rows) + +-- case 2. followed by UPDATE +TRUNCATE target_table; +BEGIN; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +UPDATE target_table SET b=array_append(b, a); +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3,-4} + -3 | {-3} + -2 | {4,6,-2} + -1 | {1,2,3,-1} +(4 rows) + +-- case 3. followed by multi-row INSERT +TRUNCATE target_table; +BEGIN; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +INSERT INTO target_table VALUES (-5, ARRAY[10,11]), (-6, ARRAY[11,12]), (-7, ARRAY[999]); +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -7 | {999} + -6 | {11,12} + -5 | {10,11} + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(7 rows) + +-- case 4. followed by distributed INSERT/SELECT +TRUNCATE target_table; +BEGIN; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +INSERT INTO target_table SELECT * FROM target_table; +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -4 | {3} + -3 | {} + -3 | {} + -2 | {4,6} + -2 | {4,6} + -1 | {1,2,3} + -1 | {1,2,3} +(8 rows) + +-- case 5. preceded by DELETE +TRUNCATE target_table; +BEGIN; +DELETE FROM target_table; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +-- case 6. preceded by UPDATE +TRUNCATE target_table; +BEGIN; +UPDATE target_table SET b=array_append(b, a); +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +-- case 7. preceded by multi-row INSERT +TRUNCATE target_table; +BEGIN; +INSERT INTO target_table VALUES (-5, ARRAY[10,11]), (-6, ARRAY[11,12]), (-7, ARRAY[999]); +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -7 | {999} + -6 | {11,12} + -5 | {10,11} + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(7 rows) + +-- case 8. preceded by distributed INSERT/SELECT +TRUNCATE target_table; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +BEGIN; +INSERT INTO target_table SELECT * FROM target_table; +INSERT INTO target_table SELECT mapped_key, c FROM source_table; +END; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -4 | {3} + -4 | {3} + -3 | {} + -3 | {} + -3 | {} + -2 | {4,6} + -2 | {4,6} + -2 | {4,6} + -1 | {1,2,3} + -1 | {1,2,3} + -1 | {1,2,3} +(12 rows) + +-- +-- repartitioned INSERT/SELECT with RETURNING +-- +TRUNCATE target_table; +SET client_min_messages TO DEBUG1; +WITH c AS ( + INSERT INTO target_table + SELECT mapped_key, c FROM source_table + RETURNING *) +SELECT * FROM c ORDER by a; +DEBUG: generating subplan XXX_1 for CTE c: INSERT INTO insert_select_repartition.target_table (a, b) SELECT mapped_key, c FROM insert_select_repartition.source_table RETURNING target_table.a, target_table.b +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +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_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer[])) c ORDER BY a +DEBUG: performing repartitioned INSERT ... SELECT + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +RESET client_min_messages; +-- +-- in combination with CTEs +-- +TRUNCATE target_table; +SET client_min_messages TO DEBUG1; +WITH t AS ( + SELECT mapped_key, a, c FROM source_table + WHERE a > floor(random()) +) +INSERT INTO target_table +SELECT mapped_key, c FROM t NATURAL JOIN source_table; +DEBUG: volatile functions are not allowed in distributed INSERT ... SELECT queries +DEBUG: generating subplan XXX_1 for CTE t: SELECT mapped_key, a, c FROM insert_select_repartition.source_table WHERE ((a)::double precision OPERATOR(pg_catalog.>) floor(random())) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT mapped_key AS a, auto_coerced_by_citus_1 AS b FROM (SELECT t.mapped_key, (t.c)::integer[] AS auto_coerced_by_citus_1 FROM ((SELECT intermediate_result.mapped_key, intermediate_result.a, intermediate_result.c FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(mapped_key integer, a integer, c double precision[])) t JOIN insert_select_repartition.source_table USING (mapped_key, a, c))) citus_insert_select_subquery +DEBUG: performing repartitioned INSERT ... SELECT +RESET client_min_messages; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + -4 | {3} + -3 | {} + -2 | {4,6} + -1 | {1,2,3} +(4 rows) + +DROP TABLE source_table, target_table; +-- +-- The case where select query has a GROUP BY ... +-- +SET citus.shard_count TO 4; +CREATE TABLE source_table(a int, b int); +SELECT create_distributed_table('source_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET citus.shard_count TO 3; +CREATE TABLE target_table(a int, b int); +SELECT create_distributed_table('target_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +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: performing repartitioned INSERT ... SELECT +RESET client_min_messages; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | 9 + 1 | 49 + 2 | 121 + 3 | 225 + 4 | 361 + 5 | 400 +(6 rows) + +-- +-- EXPLAIN output should specify repartitioned INSERT/SELECT +-- +EXPLAIN INSERT INTO target_table SELECT a, max(b) FROM source_table GROUP BY a; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) (cost=0.00..0.00 rows=0 width=0) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) (cost=0.00..0.00 rows=100000 width=8) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> HashAggregate (cost=43.90..45.90 rows=200 width=8) + Group Key: a + -> Seq Scan on source_table_4213606 source_table (cost=0.00..32.60 rows=2260 width=8) +(10 rows) + +-- +-- EXPLAIN ANALYZE is currently not supported +-- +EXPLAIN ANALYZE INSERT INTO target_table SELECT a, max(b) FROM source_table GROUP BY a; +ERROR: EXPLAIN ANALYZE is currently not supported for INSERT ... SELECT commands with repartitioning +-- +-- Duplicate names in target list +-- +TRUNCATE target_table; +SET client_min_messages TO DEBUG2; +INSERT INTO target_table + SELECT max(b), max(b) FROM source_table GROUP BY a; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +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' +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,repartitioned_results_xxxxx_from_4213609_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_4213606_to_2,repartitioned_results_xxxxx_from_4213607_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +RESET client_min_messages; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + 9 | 9 + 49 | 49 + 121 | 121 + 225 | 225 + 361 | 361 + 400 | 400 +(6 rows) + +-- +-- Prepared INSERT/SELECT +-- +TRUNCATE target_table; +PREPARE insert_plan(int, int) AS +INSERT INTO target_table + SELECT a, max(b) FROM source_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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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; + a | count | distinct_values +--------------------------------------------------------------------- + 0 | 6 | 1 + 1 | 6 | 1 + 2 | 12 | 1 + 3 | 6 | 1 + 4 | 6 | 1 +(5 rows) + +DEALLOCATE insert_plan; +-- +-- Prepared router INSERT/SELECT. We currently use pull to coordinator when the +-- distributed query has a single task. +-- +TRUNCATE target_table; +PREPARE insert_plan(int) AS +INSERT INTO target_table + SELECT a, max(b) FROM source_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: 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: 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: 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: 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: 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: 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: 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; + a | count | distinct_values +--------------------------------------------------------------------- + 0 | 7 | 1 +(1 row) + +DEALLOCATE insert_plan; +-- +-- Prepared INSERT/SELECT with no parameters. +-- +TRUNCATE target_table; +PREPARE insert_plan AS +INSERT INTO target_table + SELECT a, max(b) FROM source_table + WHERE a BETWEEN 1 AND 2 GROUP BY a; +SELECT public.coordinator_plan($Q$ +EXPLAIN EXECUTE insert_plan; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) (cost=0.00..0.00 rows=0 width=0) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) (cost=0.00..0.00 rows=100000 width=8) + Task Count: 4 +(4 rows) + +SET client_min_messages TO DEBUG1; +EXECUTE insert_plan; +DEBUG: performing repartitioned INSERT ... SELECT +EXECUTE insert_plan; +DEBUG: performing repartitioned INSERT ... SELECT +EXECUTE insert_plan; +DEBUG: performing repartitioned INSERT ... SELECT +EXECUTE insert_plan; +DEBUG: performing repartitioned INSERT ... SELECT +EXECUTE insert_plan; +DEBUG: performing repartitioned INSERT ... SELECT +EXECUTE insert_plan; +DEBUG: performing repartitioned INSERT ... SELECT +EXECUTE insert_plan; +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; + a | count | distinct_values +--------------------------------------------------------------------- + 1 | 7 | 1 + 2 | 7 | 1 +(2 rows) + +DEALLOCATE insert_plan; +-- +-- INSERT/SELECT in CTE +-- +TRUNCATE target_table; +SET client_min_messages TO DEBUG2; +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: 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: 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 +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: performing repartitioned INSERT ... SELECT +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) RETURNING citus_table_alias.a, citus_table_alias.b +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) RETURNING citus_table_alias.a, citus_table_alias.b +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) RETURNING citus_table_alias.a, citus_table_alias.b +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 client_min_messages; +SELECT * FROM target_table ORDER BY a, b; + a | b +--------------------------------------------------------------------- + 0 | 1 + 0 | 4 + 0 | 9 + 0 | 9 + 1 | 16 + 1 | 25 + 1 | 36 + 1 | 49 + 1 | 49 + 2 | 64 + 2 | 81 + 2 | 100 + 2 | 121 + 2 | 121 + 3 | 144 + 3 | 169 + 3 | 196 + 3 | 225 + 3 | 225 + 4 | 256 + 4 | 289 + 4 | 324 + 4 | 361 + 4 | 361 + 5 | 400 + 5 | 400 +(26 rows) + +DROP TABLE source_table, target_table; +-- +-- Constraint failure and rollback +-- +SET citus.shard_count TO 4; +CREATE TABLE source_table(a int, b int); +SELECT create_distributed_table('source_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table SELECT i, i * i FROM generate_series(1, 10) i; +UPDATE source_table SET b = NULL where b IN (9, 4); +SET citus.shard_replication_factor TO 2; +CREATE TABLE target_table(a int, b int not null); +SELECT create_distributed_table('target_table', 'a', 'range'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CALL public.create_range_partitioned_shards('target_table', '{0,3,6,9}','{2,5,8,50}'); +INSERT INTO target_table VALUES (11,9), (22,4); +EXPLAIN (costs off) INSERT INTO target_table SELECT * FROM source_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on source_table_4213613 source_table +(8 rows) + +EXPLAIN (costs off) INSERT INTO target_table SELECT * FROM source_table WHERE b IS NOT NULL; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on source_table_4213613 source_table + Filter: (b IS NOT NULL) +(9 rows) + +BEGIN; +SAVEPOINT s1; +INSERT INTO target_table SELECT * FROM source_table; +ERROR: null value in column "b" violates not-null constraint +ROLLBACK TO SAVEPOINT s1; +INSERT INTO target_table SELECT * FROM source_table WHERE b IS NOT NULL; +END; +SELECT * FROM target_table ORDER BY b; + a | b +--------------------------------------------------------------------- + 1 | 1 + 22 | 4 + 11 | 9 + 4 | 16 + 5 | 25 + 6 | 36 + 7 | 49 + 8 | 64 + 9 | 81 + 10 | 100 +(10 rows) + +-- verify that values have been replicated to both replicas +SELECT * FROM run_command_on_placements('target_table', 'select count(*) from %s') ORDER BY shardid, nodeport; + nodename | nodeport | shardid | success | result +--------------------------------------------------------------------- + localhost | 57637 | 4213617 | t | 1 + localhost | 57638 | 4213617 | t | 1 + localhost | 57637 | 4213618 | t | 2 + localhost | 57638 | 4213618 | t | 2 + localhost | 57637 | 4213619 | t | 3 + localhost | 57638 | 4213619 | t | 3 + localhost | 57637 | 4213620 | t | 4 + localhost | 57638 | 4213620 | t | 4 +(8 rows) + +-- +-- Multiple casts in the SELECT query +-- +TRUNCATE target_table; +SET client_min_messages TO DEBUG2; +INSERT INTO target_table SELECT 1.12, b::bigint FROM source_table WHERE b IS NOT NULL; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +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' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213617 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213613_to_0,repartitioned_results_xxxxx_from_4213614_to_0,repartitioned_results_xxxxx_from_4213615_to_0,repartitioned_results_xxxxx_from_4213616_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +RESET client_min_messages; +SELECT * FROM target_table ORDER BY a, b; + a | b +--------------------------------------------------------------------- + 1 | 1 + 1 | 16 + 1 | 25 + 1 | 36 + 1 | 49 + 1 | 64 + 1 | 81 + 1 | 100 +(8 rows) + +-- +-- ROLLBACK after out of range error +-- +TRUNCATE target_table; +BEGIN; +INSERT INTO target_table SELECT a * 10, b FROM source_table WHERE b IS NOT NULL; +ERROR: could not find shard for partition column value +END; +SELECT max(result) FROM run_command_on_placements('target_table', 'select count(*) from %s'); + max +--------------------------------------------------------------------- + 0 +(1 row) + +DROP TABLE source_table, target_table; +-- +-- Range partitioned target's ranges doesn't cover the whole range +-- +SET citus.shard_replication_factor TO 2; +SET citus.shard_count TO 4; +CREATE TABLE source_table(a int, b int); +SELECT create_distributed_table('source_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table SELECT i, i * i FROM generate_series(1, 10) i; +SET citus.shard_replication_factor TO 2; +CREATE TABLE target_table(b int not null, a float); +SELECT create_distributed_table('target_table', 'a', 'range'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CALL public.create_range_partitioned_shards('target_table', '{0.0,3.5,6.5,9.5}','{2.9,5.9,8.9,50.0}'); +INSERT INTO target_table SELECT b, a+0.6 FROM source_table; +SELECT * FROM target_table ORDER BY a; + b | a +--------------------------------------------------------------------- + 1 | 1.6 + 4 | 2.6 + 9 | 3.6 + 16 | 4.6 + 25 | 5.6 + 36 | 6.6 + 49 | 7.6 + 64 | 8.6 + 81 | 9.6 + 100 | 10.6 +(10 rows) + +-- verify that values have been replicated to both replicas, and that each +-- replica has received correct number of rows +SELECT * FROM run_command_on_placements('target_table', 'select count(*) from %s') ORDER BY shardid, nodeport; + nodename | nodeport | shardid | success | result +--------------------------------------------------------------------- + localhost | 57637 | 4213625 | t | 2 + localhost | 57638 | 4213625 | t | 2 + localhost | 57637 | 4213626 | t | 3 + localhost | 57638 | 4213626 | t | 3 + localhost | 57637 | 4213627 | t | 3 + localhost | 57638 | 4213627 | t | 3 + localhost | 57637 | 4213628 | t | 2 + localhost | 57638 | 4213628 | t | 2 +(8 rows) + +DROP TABLE source_table, target_table; +-- +-- Select column names should be unique +-- +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 4; +CREATE TABLE source_table(a int, b int); +SELECT create_distributed_table('source_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET citus.shard_count TO 3; +CREATE TABLE target_table(a int, b int, c int, d int, e int, f int); +SELECT create_distributed_table('target_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'a' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213633 AS citus_table_alias (a, b, c, d) SELECT a, b, c, d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213629_to_0,repartitioned_results_xxxxx_from_4213630_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213634 AS citus_table_alias (a, b, c, d) SELECT a, b, c, d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213630_to_1,repartitioned_results_xxxxx_from_4213631_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213635 AS citus_table_alias (a, b, c, d) SELECT a, b, c, d FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213632_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer, c integer, d integer) +RESET client_min_messages; +SELECT count(*) FROM target_table; + count +--------------------------------------------------------------------- + 10 +(1 row) + +-- +-- Disable repartitioned insert/select +-- +TRUNCATE target_table; +SET citus.enable_repartitioned_insert_select TO OFF; +EXPLAIN (costs off) INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 AS aa FROM source_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on source_table_4213629 source_table +(8 rows) + +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: Router planner cannot handle multi-shard select queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +RESET client_min_messages; +SELECT count(*) FROM target_table; + count +--------------------------------------------------------------------- + 10 +(1 row) + +SET citus.enable_repartitioned_insert_select TO ON; +EXPLAIN (costs off) INSERT INTO target_table SELECT a AS aa, b AS aa, 1 AS aa, 2 AS aa FROM source_table; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on source_table_4213629 source_table +(8 rows) + +DROP TABLE source_table, target_table; +-- +-- Don't use INSERT/SELECT repartition with repartition joins +-- +create table test(x int, y int); +select create_distributed_table('test', 'x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +set citus.enable_repartition_joins to true; +INSERT INTO test SELECT i, i FROM generate_series(1, 10) i; +EXPLAIN (costs off) INSERT INTO test(y, x) SELECT a.x, b.y FROM test a JOIN test b USING (y); + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: None, not supported for re-partition queries + -> MapMergeJob + Map Task Count: 3 + Merge Task Count: 4 + -> MapMergeJob + Map Task Count: 3 + Merge Task Count: 4 +(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: Collecting INSERT ... SELECT results on coordinator +RESET client_min_messages; +SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 20 +(1 row) + +TRUNCATE test; +INSERT INTO test SELECT i, i FROM generate_series(1, 10) i; +EXPLAIN (costs off) INSERT INTO test SELECT a.* FROM test a JOIN test b USING (y); + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: None, not supported for re-partition queries + -> MapMergeJob + Map Task Count: 3 + Merge Task Count: 4 + -> MapMergeJob + Map Task Count: 3 + Merge Task Count: 4 +(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: Collecting INSERT ... SELECT results on coordinator +RESET client_min_messages; +SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 20 +(1 row) + +-- +-- In the following case we coerce some columns and move uncoerced versions to the +-- end of SELECT list. The following case verifies that we rename those columns so +-- we don't get "column reference is ambiguous" errors. +-- +CREATE TABLE target_table( + c1 int, + c2 int, + c3 timestamp, + a int, + b int, + c int, + c4 int, + c5 int, + c6 int[], + cardinality int, + sum int, + PRIMARY KEY (c1, c2, c3, c4, c5, c6) +); +SET citus.shard_count TO 5; +SELECT create_distributed_table('target_table', 'c1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE source_table( + c1 int, + c2 int, + c3 date, + c4 int, + cardinality int, + sum int +); +SET citus.shard_count TO 4; +SELECT create_distributed_table('source_table', 'c1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION dist_func(a int, b int) RETURNS int[] +AS $$ +BEGIN + RETURN array_fill(a, ARRAY[b]); +END; +$$ +LANGUAGE plpgsql STABLE; +SELECT create_distributed_function('dist_func(int, int)'); +NOTICE: procedure insert_select_repartition.dist_func is already distributed + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG; +SET citus.enable_unique_job_ids TO off; +INSERT INTO source_table VALUES (1,2, '2020-02-02', 3, 4, 5); +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +INSERT INTO source_table VALUES (1,2, '2020-02-02', 3, 4, 5); +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +INSERT INTO source_table VALUES (3,4, '2020-02-02', 3, 4, 5); +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 3 +INSERT INTO target_table AS enriched(c1, c2, c3, c4, c5, c6, cardinality, sum) +SELECT c1, c2, c3, c4, -1::float AS c5, + dist_func(c1, 4) c6, + sum(cardinality), + sum(sum) +FROM source_table +GROUP BY c1, c2, c3, c4, c5, c6 +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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'c1' +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213639 AS enriched (c1, c2, c3, c4, c5, c6, cardinality, sum) SELECT c1, c2, c3, c4, c5, c6, cardinality, sum FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213644_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(c1 integer, c2 integer, c3 timestamp without time zone, c4 integer, c5 integer, c6 integer[], cardinality integer, sum integer) ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = (enriched.cardinality OPERATOR(pg_catalog.+) excluded.cardinality), sum = (enriched.sum OPERATOR(pg_catalog.+) excluded.sum) +DEBUG: distributed statement: INSERT INTO insert_select_repartition.target_table_4213641 AS enriched (c1, c2, c3, c4, c5, c6, cardinality, sum) SELECT c1, c2, c3, c4, c5, c6, cardinality, sum FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213645_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(c1 integer, c2 integer, c3 timestamp without time zone, c4 integer, c5 integer, c6 integer[], cardinality integer, sum integer) ON CONFLICT(c1, c2, c3, c4, c5, c6) DO UPDATE SET cardinality = (enriched.cardinality OPERATOR(pg_catalog.+) excluded.cardinality), sum = (enriched.sum OPERATOR(pg_catalog.+) excluded.sum) +RESET client_min_messages; +EXPLAIN (COSTS OFF) INSERT INTO target_table AS enriched(c1, c2, c3, c4, c5, c6, cardinality, sum) +SELECT c1, c2, c3, c4, -1::float AS c5, + dist_func(c1, 4) c6, + sum(cardinality), + sum(sum) +FROM source_table +GROUP BY c1, c2, c3, c4, c5, c6 +ON CONFLICT(c1, c2, c3, c4, c5, c6) +DO UPDATE SET + cardinality = enriched.cardinality + excluded.cardinality, + sum = enriched.sum + excluded.sum; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> HashAggregate + Group Key: c1, c2, c3, c4, '-1'::double precision, insert_select_repartition.dist_func(c1, 4) + -> Seq Scan on source_table_4213644 source_table +(10 rows) + +-- verify that we don't report repartitioned insert/select for tables +-- with sequences. See https://github.com/citusdata/citus/issues/3936 +create table table_with_sequences (x int, y int, z bigserial); +insert into table_with_sequences values (1,1); +select create_distributed_table('table_with_sequences','x'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT public.plan_without_result_lines($Q$ +explain (costs off) insert into table_with_sequences select y, x from table_with_sequences; +$Q$); + plan_without_result_lines +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on table_with_sequences_4213648 table_with_sequences +(8 rows) + +-- verify that we don't report repartitioned insert/select for tables +-- with user-defined sequences. +CREATE SEQUENCE user_defined_sequence; +create table table_with_user_sequences (x int, y int, z bigint default nextval('user_defined_sequence')); +insert into table_with_user_sequences values (1,1); +select create_distributed_table('table_with_user_sequences','x'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT public.plan_without_result_lines($Q$ +explain (costs off) insert into table_with_user_sequences select y, x from table_with_user_sequences; +$Q$); + plan_without_result_lines +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on table_with_user_sequences_4213652 table_with_user_sequences +(8 rows) + +-- clean-up +SET client_min_messages TO WARNING; +DROP SCHEMA insert_select_repartition CASCADE; diff --git a/src/test/regress/expected/intermediate_result_pruning.out b/src/test/regress/expected/intermediate_result_pruning.out index 2670c48a6..f6cf8c1e1 100644 --- a/src/test/regress/expected/intermediate_result_pruning.out +++ b/src/test/regress/expected/intermediate_result_pruning.out @@ -1,3 +1,17 @@ +-- +-- INTERMEDIATE_RESULT_PRUNING +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA intermediate_result_pruning; SET search_path TO intermediate_result_pruning; SET citus.log_intermediate_results TO TRUE; @@ -1039,7 +1053,7 @@ inserts AS MATERIALIZED ( RETURNING * ) 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: 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: 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 diff --git a/src/test/regress/expected/intermediate_result_pruning_0.out b/src/test/regress/expected/intermediate_result_pruning_0.out new file mode 100644 index 000000000..4ae6b8e16 --- /dev/null +++ b/src/test/regress/expected/intermediate_result_pruning_0.out @@ -0,0 +1,1073 @@ +-- +-- INTERMEDIATE_RESULT_PRUNING +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA intermediate_result_pruning; +SET search_path TO intermediate_result_pruning; +SET citus.log_intermediate_results TO TRUE; +SET citus.shard_count TO 4; +SET citus.next_shard_id TO 1480000; +SET citus.shard_replication_factor = 1; +CREATE TABLE table_1 (key int, value text); +SELECT create_distributed_table('table_1', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE table_2 (key int, value text); +SELECT create_distributed_table('table_2', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE table_3 (key int, value text); +SELECT create_distributed_table('table_3', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE ref_table (key int, value text); +SELECT create_reference_table('ref_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +-- load some data +INSERT INTO table_1 VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'); +INSERT INTO table_2 VALUES (3, '3'), (4, '4'), (5, '5'), (6, '6'); +INSERT INTO table_3 VALUES (3, '3'), (4, '4'), (5, '5'), (6, '6'); +INSERT INTO ref_table VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'), (6, '6'); +-- see which workers are hit for intermediate results +SET client_min_messages TO DEBUG1; +-- a very basic case, where the intermediate result +-- should go to both workers +WITH some_values_1 AS MATERIALIZED + (SELECT key FROM table_1 WHERE value IN ('3', '4')) +SELECT + count(*) +FROM + some_values_1 JOIN table_2 USING (key); +DEBUG: generating subplan XXX_1 for CTE some_values_1: SELECT key 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 FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) some_values_1 JOIN intermediate_result_pruning.table_2 USING (key)) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- a very basic case, where the intermediate result +-- should only go to one worker because the final query is a router +-- we use random() to prevent postgres inline the CTE(s) +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')) +SELECT + count(*) +FROM + some_values_1 JOIN table_2 USING (key) WHERE table_2.key = 1; +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.table_2 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 1) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- a similar query, but with a reference table now +-- given that reference tables are replicated to all nodes +-- we have to broadcast to all nodes +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')) +SELECT + count(*) +FROM + some_values_1 JOIN ref_table USING (key); +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 + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- a similar query as above, but this time use the CTE inside +-- another CTE +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1) +SELECT + count(*) +FROM + some_values_2 JOIN table_2 USING (key) WHERE table_2.key = 1; +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: generating subplan XXX_2 for CTE some_values_2: SELECT key, random() AS random 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 +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 1) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- the second CTE does a join with a distributed table +-- and the final query is a router query +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key)) +SELECT + count(*) +FROM + some_values_2 JOIN table_2 USING (key) WHERE table_2.key = 3; +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 3) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- the first CTE is used both within second CTE and the final query +-- the second CTE does a join with a distributed table +-- and the final query is a router query +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key)) +SELECT + count(*) +FROM + (some_values_2 JOIN table_2 USING (key)) JOIN some_values_1 USING (key) WHERE table_2.key = 3; +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) JOIN (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 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 3) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- the first CTE is used both within second CTE and the final query +-- the second CTE does a join with a distributed table but a router query on a worker +-- and the final query is another router query on another worker +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key) WHERE table_2.key = 1) +SELECT + count(*) +FROM + (some_values_2 JOIN table_2 USING (key)) JOIN some_values_1 USING (key) WHERE table_2.key = 3; +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 1) +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) JOIN (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 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 3) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- the first CTE is used both within second CTE and the final query +-- the second CTE does a join with a distributed table but a router query on a worker +-- and the final query is a router query on the same worker, so the first result is only +-- broadcasted to a single node +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key) WHERE table_2.key = 1) +SELECT + count(*) +FROM + (some_values_2 JOIN table_2 USING (key)) JOIN some_values_1 USING (key) WHERE table_2.key = 1; +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 1) +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) JOIN (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 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 1) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- the same query with the above, but the final query is hitting all shards +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key)) +SELECT + count(*) +FROM + (some_values_2 JOIN table_2 USING (key)) JOIN some_values_1 USING (key) WHERE table_2.key != 3; +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) JOIN (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 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.<>) 3) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +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 + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- even if we add a filter on the first query and make it a router query, +-- the first intermediate result still hits all workers because of the final +-- join is hitting all workers +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key) WHERE table_2.key = 3) +SELECT + count(*) +FROM + (some_values_2 JOIN table_2 USING (key)) JOIN some_values_1 USING (key) WHERE table_2.key != 3; +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 3) +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) JOIN (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 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.<>) 3) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +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 + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- the reference table is joined with a distributed table and an intermediate +-- result, but the distributed table hits all shards, so the intermediate +-- result is sent to all nodes +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM ref_table WHERE value IN ('3', '4')) +SELECT + count(*) +FROM + (some_values_1 JOIN ref_table USING (key)) JOIN table_2 USING (key); +DEBUG: generating subplan XXX_1 for CTE some_values_1: SELECT key, random() AS random FROM intermediate_result_pruning.ref_table 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)) JOIN intermediate_result_pruning.table_2 USING (key)) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- similar query as above, but this time the whole query is a router +-- query, so no intermediate results +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM ref_table WHERE value IN ('3', '4')) +SELECT + count(*) +FROM + (some_values_1 JOIN ref_table USING (key)) JOIN table_2 USING (key) WHERE table_2.key = 1; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- now, the second CTE has a single shard join with a distributed table +-- so the first CTE should only be broadcasted to that node +-- since the final query doesn't have a join, it should simply be broadcasted +-- to one node +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key) WHERE key = 1) +SELECT + count(*) +FROM + some_values_2; +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) WHERE (some_values_1.key OPERATOR(pg_catalog.=) 1) +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- the same query inlined inside a CTE, and the final query has a +-- join with a distributed table +WITH top_cte as MATERIALIZED ( + WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key) WHERE key = 1) + SELECT + DISTINCT key + FROM + some_values_2 +) +SELECT + count(*) +FROM + top_cte JOIN table_2 USING (key); +DEBUG: generating subplan XXX_1 for CTE top_cte: WITH some_values_1 AS MATERIALIZED (SELECT table_1.key, random() AS random FROM intermediate_result_pruning.table_1 WHERE (table_1.value OPERATOR(pg_catalog.=) ANY (ARRAY['3'::text, '4'::text]))), some_values_2 AS MATERIALIZED (SELECT some_values_1.key, random() AS random FROM (some_values_1 JOIN intermediate_result_pruning.table_2 USING (key)) WHERE (some_values_1.key OPERATOR(pg_catalog.=) 1)) SELECT DISTINCT key FROM some_values_2 +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) WHERE (some_values_1.key OPERATOR(pg_catalog.=) 1) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT DISTINCT key FROM (SELECT intermediate_result.key, intermediate_result.random FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) top_cte JOIN intermediate_result_pruning.table_2 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 +DEBUG: Subplan XXX_2 will be written to local file + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- very much the same query, but this time the top query is also a router query +-- on a single worker, so all intermediate results only hit a single node +WITH top_cte as MATERIALIZED ( + WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key) WHERE key = 1) + SELECT + DISTINCT key + FROM + some_values_2 +) +SELECT + count(*) +FROM + top_cte JOIN table_2 USING (key) WHERE table_2.key = 2; +DEBUG: generating subplan XXX_1 for CTE top_cte: WITH some_values_1 AS MATERIALIZED (SELECT table_1.key, random() AS random FROM intermediate_result_pruning.table_1 WHERE (table_1.value OPERATOR(pg_catalog.=) ANY (ARRAY['3'::text, '4'::text]))), some_values_2 AS MATERIALIZED (SELECT some_values_1.key, random() AS random FROM (some_values_1 JOIN intermediate_result_pruning.table_2 USING (key)) WHERE (some_values_1.key OPERATOR(pg_catalog.=) 1)) SELECT DISTINCT key FROM some_values_2 +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) WHERE (some_values_1.key OPERATOR(pg_catalog.=) 1) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT DISTINCT key FROM (SELECT intermediate_result.key, intermediate_result.random FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) top_cte JOIN intermediate_result_pruning.table_2 USING (key)) WHERE (table_2.key OPERATOR(pg_catalog.=) 2) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- some_values_1 is first used by a single shard-query, and than with a multi-shard +-- CTE, finally a cartesian product join +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1 JOIN table_2 USING (key) WHERE key = 1), + some_values_3 AS MATERIALIZED + (SELECT key FROM (some_values_2 JOIN table_2 USING (key)) JOIN some_values_1 USING (key)) +SELECT * FROM some_values_3 JOIN ref_table ON (true); +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: generating subplan XXX_2 for CTE some_values_2: SELECT some_values_1.key, random() AS random 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.table_2 USING (key)) WHERE (some_values_1.key OPERATOR(pg_catalog.=) 1) +DEBUG: generating subplan XXX_3 for CTE some_values_3: SELECT some_values_2.key FROM (((SELECT intermediate_result.key, intermediate_result.random FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN intermediate_result_pruning.table_2 USING (key)) JOIN (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 USING (key)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT some_values_3.key, ref_table.key, ref_table.value FROM ((SELECT intermediate_result.key FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) some_values_3 JOIN intermediate_result_pruning.ref_table ON (true)) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +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 + key | key | value +--------------------------------------------------------------------- +(0 rows) + +-- join on intermediate results, so should only +-- go to a single node +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM table_2 WHERE value IN ('3', '4')) +SELECT count(*) FROM some_values_2 JOIN some_values_1 USING (key); +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: generating subplan XXX_2 for CTE some_values_2: SELECT key, random() AS random FROM intermediate_result_pruning.table_2 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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN (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 USING (key)) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- same query with WHERE false make sure that we're not broken +-- for such edge cases +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM table_2 WHERE value IN ('3', '4')) +SELECT count(*) FROM some_values_2 JOIN some_values_1 USING (key) WHERE false; +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: generating subplan XXX_2 for CTE some_values_2: SELECT key, random() AS random FROM intermediate_result_pruning.table_2 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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_2 JOIN (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 USING (key)) WHERE false +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- do not use some_values_2 at all, so only 2 intermediate results are +-- broadcasted +WITH some_values_1 AS MATERIALIZED + (SELECT key, random() FROM table_1 WHERE value IN ('3', '4')), + some_values_2 AS MATERIALIZED + (SELECT key, random() FROM some_values_1), + some_values_3 AS MATERIALIZED + (SELECT key, random() FROM some_values_1) +SELECT + count(*) +FROM + some_values_3; +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: generating subplan XXX_2 for CTE some_values_3: SELECT key, random() AS random 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 +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_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, random double precision)) some_values_3 +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- lets have some deeper intermediate results +-- the inner most two results and the final query (which contains only intermediate results) +-- hitting single worker, others hitting all workers +-- (see below query where all intermediate results hit a single node) +SELECT count(*) FROM +( + SELECT avg(min::int) FROM + ( + SELECT min(table_1.value) FROM + ( + SELECT avg(value::int) as avg_ev_type FROM + ( + SELECT max(value) as mx_val_1 FROM + ( + SELECT avg(value::int) as avg FROM + ( + SELECT cnt FROM + ( + SELECT count(*) as cnt, value + FROM table_1 + WHERE key = 1 + GROUP BY value + ) as level_1, table_1 + WHERE table_1.key = level_1.cnt AND key = 3 + ) as level_2, table_2 + WHERE table_2.key = level_2.cnt AND key = 5 + GROUP BY level_2.cnt + ) as level_3, table_1 + WHERE value::numeric = level_3.avg AND key = 6 + GROUP BY level_3.avg + ) as level_4, table_2 + WHERE level_4.mx_val_1::int = table_2.key + GROUP BY level_4.mx_val_1 + ) as level_5, table_1 + WHERE level_5.avg_ev_type = table_1.key AND key > 111 + GROUP BY level_5.avg_ev_type + ) as level_6, table_1 WHERE table_1.key::int = level_6.min::int + GROUP BY table_1.value +) as bar; +DEBUG: generating subplan XXX_1 for subquery SELECT count(*) AS cnt, value FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 1) GROUP BY value +DEBUG: generating subplan XXX_2 for subquery SELECT avg((table_2.value)::integer) AS avg FROM (SELECT level_1.cnt FROM (SELECT intermediate_result.cnt, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(cnt bigint, value text)) level_1, intermediate_result_pruning.table_1 WHERE ((table_1.key OPERATOR(pg_catalog.=) level_1.cnt) AND (table_1.key OPERATOR(pg_catalog.=) 3))) level_2, intermediate_result_pruning.table_2 WHERE ((table_2.key OPERATOR(pg_catalog.=) level_2.cnt) AND (table_2.key OPERATOR(pg_catalog.=) 5)) GROUP BY level_2.cnt +DEBUG: generating subplan XXX_3 for subquery SELECT max(table_1.value) AS mx_val_1 FROM (SELECT intermediate_result.avg FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(avg numeric)) level_3, intermediate_result_pruning.table_1 WHERE (((table_1.value)::numeric OPERATOR(pg_catalog.=) level_3.avg) AND (table_1.key OPERATOR(pg_catalog.=) 6)) GROUP BY level_3.avg +DEBUG: generating subplan XXX_4 for subquery SELECT avg((table_2.value)::integer) AS avg_ev_type FROM (SELECT intermediate_result.mx_val_1 FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(mx_val_1 text)) level_4, intermediate_result_pruning.table_2 WHERE ((level_4.mx_val_1)::integer OPERATOR(pg_catalog.=) table_2.key) GROUP BY level_4.mx_val_1 +DEBUG: generating subplan XXX_5 for subquery SELECT min(table_1.value) AS min FROM (SELECT intermediate_result.avg_ev_type FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(avg_ev_type numeric)) level_5, intermediate_result_pruning.table_1 WHERE ((level_5.avg_ev_type OPERATOR(pg_catalog.=) (table_1.key)::numeric) AND (table_1.key OPERATOR(pg_catalog.>) 111)) GROUP BY level_5.avg_ev_type +DEBUG: generating subplan XXX_6 for subquery SELECT avg((level_6.min)::integer) AS avg FROM (SELECT intermediate_result.min FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result(min text)) level_6, intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) (level_6.min)::integer) GROUP BY table_1.value +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.avg FROM read_intermediate_result('XXX_6'::text, 'binary'::citus_copy_format) intermediate_result(avg numeric)) bar +DEBUG: Subplan XXX_1 will be written to local file +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_4 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_4 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_5 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_5 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_6 will be written to local file + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- the same query where all intermediate results hits one +-- worker because each and every query is a router query -- but on different nodes +SELECT count(*) FROM +( + SELECT avg(min::int) FROM + ( + SELECT min(table_1.value) FROM + ( + SELECT avg(value::int) as avg_ev_type FROM + ( + SELECT max(value) as mx_val_1 FROM + ( + SELECT avg(value::int) as avg FROM + ( + SELECT cnt FROM + ( + SELECT count(*) as cnt, value + FROM table_1 + WHERE key = 1 + GROUP BY value + ) as level_1, table_1 + WHERE table_1.key = level_1.cnt AND key = 3 + ) as level_2, table_2 + WHERE table_2.key = level_2.cnt AND key = 5 + GROUP BY level_2.cnt + ) as level_3, table_1 + WHERE value::numeric = level_3.avg AND key = 6 + GROUP BY level_3.avg + ) as level_4, table_2 + WHERE level_4.mx_val_1::int = table_2.key AND table_2.key = 1 + GROUP BY level_4.mx_val_1 + ) as level_5, table_1 + WHERE level_5.avg_ev_type = table_1.key AND key = 111 + GROUP BY level_5.avg_ev_type + ) as level_6, table_1 + WHERE table_1.key::int = level_6.min::int AND table_1.key = 4 + GROUP BY table_1.value +) as bar; +DEBUG: generating subplan XXX_1 for subquery SELECT count(*) AS cnt, value FROM intermediate_result_pruning.table_1 WHERE (key OPERATOR(pg_catalog.=) 1) GROUP BY value +DEBUG: generating subplan XXX_2 for subquery SELECT avg((table_2.value)::integer) AS avg FROM (SELECT level_1.cnt FROM (SELECT intermediate_result.cnt, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(cnt bigint, value text)) level_1, intermediate_result_pruning.table_1 WHERE ((table_1.key OPERATOR(pg_catalog.=) level_1.cnt) AND (table_1.key OPERATOR(pg_catalog.=) 3))) level_2, intermediate_result_pruning.table_2 WHERE ((table_2.key OPERATOR(pg_catalog.=) level_2.cnt) AND (table_2.key OPERATOR(pg_catalog.=) 5)) GROUP BY level_2.cnt +DEBUG: generating subplan XXX_3 for subquery SELECT max(table_1.value) AS mx_val_1 FROM (SELECT intermediate_result.avg FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(avg numeric)) level_3, intermediate_result_pruning.table_1 WHERE (((table_1.value)::numeric OPERATOR(pg_catalog.=) level_3.avg) AND (table_1.key OPERATOR(pg_catalog.=) 6)) GROUP BY level_3.avg +DEBUG: generating subplan XXX_4 for subquery SELECT avg((table_2.value)::integer) AS avg_ev_type FROM (SELECT intermediate_result.mx_val_1 FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(mx_val_1 text)) level_4, intermediate_result_pruning.table_2 WHERE (((level_4.mx_val_1)::integer OPERATOR(pg_catalog.=) table_2.key) AND (table_2.key OPERATOR(pg_catalog.=) 1)) GROUP BY level_4.mx_val_1 +DEBUG: generating subplan XXX_5 for subquery SELECT min(table_1.value) AS min FROM (SELECT intermediate_result.avg_ev_type FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(avg_ev_type numeric)) level_5, intermediate_result_pruning.table_1 WHERE ((level_5.avg_ev_type OPERATOR(pg_catalog.=) (table_1.key)::numeric) AND (table_1.key OPERATOR(pg_catalog.=) 111)) GROUP BY level_5.avg_ev_type +DEBUG: generating subplan XXX_6 for subquery SELECT avg((level_6.min)::integer) AS avg FROM (SELECT intermediate_result.min FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result(min text)) level_6, intermediate_result_pruning.table_1 WHERE ((table_1.key OPERATOR(pg_catalog.=) (level_6.min)::integer) AND (table_1.key OPERATOR(pg_catalog.=) 4)) GROUP BY table_1.value +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.avg FROM read_intermediate_result('XXX_6'::text, 'binary'::citus_copy_format) intermediate_result(avg numeric)) bar +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_4 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_5 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_6 will be written to local file + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- sanity checks for set operations +-- the intermediate results should just hit a single worker +(SELECT key FROM table_1 WHERE key = 1) +INTERSECT +(SELECT key FROM table_1 WHERE key = 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) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer) INTERSECT SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file + key +--------------------------------------------------------------------- +(0 rows) + +-- the intermediate results should just hit a single worker +WITH cte_1 AS MATERIALIZED +( + (SELECT key FROM table_1 WHERE key = 1) + INTERSECT + (SELECT key FROM table_1 WHERE key = 2) +), +cte_2 AS MATERIALIZED +( + (SELECT key FROM table_1 WHERE key = 3) + INTERSECT + (SELECT key FROM table_1 WHERE key = 4) +) +SELECT * FROM cte_1 + UNION +SELECT * FROM cte_2; +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) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer) INTERSECT SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer) +DEBUG: generating subplan XXX_2 for CTE cte_2: SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 3) INTERSECT SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 4) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT cte_1.key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_1 UNION SELECT cte_2.key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_2 +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file + key +--------------------------------------------------------------------- +(0 rows) + +-- one final test with SET operations, where +-- we join the results with distributed tables +-- so cte_1 should hit all workers, but still the +-- others should hit single worker each +WITH cte_1 AS MATERIALIZED +( + (SELECT key FROM table_1 WHERE key = 1) + INTERSECT + (SELECT key FROM table_1 WHERE key = 2) +), +cte_2 AS MATERIALIZED +( + SELECT count(*) FROM table_1 JOIN cte_1 USING (key) +) +SELECT * FROM cte_2; +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) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer) INTERSECT SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer) +DEBUG: generating subplan XXX_2 for CTE cte_2: SELECT count(*) AS count FROM (intermediate_result_pruning.table_1 JOIN (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_1 USING (key)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count FROM (SELECT intermediate_result.count FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(count bigint)) cte_2 +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 written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- sanity checks for non-colocated subquery joins +-- the recursively planned subquery (bar) should hit all +-- nodes +SELECT + count(*) +FROM + (SELECT key, random() FROM table_1) as foo, + (SELECT key, random() FROM table_2) as bar +WHERE + foo.key != bar.key; +DEBUG: generating subplan XXX_1 for subquery SELECT key, random() AS random FROM intermediate_result_pruning.table_2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT table_1.key, random() AS random FROM intermediate_result_pruning.table_1) foo, (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)) bar WHERE (foo.key OPERATOR(pg_catalog.<>) bar.key) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 14 +(1 row) + +-- the recursively planned subquery (bar) should hit one +-- node because foo goes to a single node +SELECT + count(*) +FROM + (SELECT key, random() FROM table_1 WHERE key = 1) as foo, + (SELECT key, random() FROM table_2) as bar +WHERE + foo.key != bar.key; +DEBUG: generating subplan XXX_1 for subquery SELECT key, random() AS random FROM intermediate_result_pruning.table_2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT table_1.key, random() AS random FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 1)) foo, (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)) bar WHERE (foo.key OPERATOR(pg_catalog.<>) bar.key) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 4 +(1 row) + +-- sanity checks for modification queries +-- select_data goes to a single node, because it is used in another subquery +-- raw_data is also the final router query, so hits a single shard +-- however, the subquery in WHERE clause of the DELETE query is broadcasted to all +-- nodes +BEGIN; +WITH select_data AS MATERIALIZED ( + SELECT * FROM table_1 +), +raw_data AS MATERIALIZED ( + DELETE FROM table_2 WHERE key >= (SELECT min(key) FROM select_data WHERE key > 1) RETURNING * +) +SELECT * FROM raw_data; +DEBUG: generating subplan XXX_1 for CTE select_data: SELECT key, value FROM intermediate_result_pruning.table_1 +DEBUG: generating subplan XXX_2 for CTE raw_data: DELETE FROM intermediate_result_pruning.table_2 WHERE (key OPERATOR(pg_catalog.>=) (SELECT min(select_data.key) AS min 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)) select_data WHERE (select_data.key OPERATOR(pg_catalog.>) 1))) RETURNING key, value +DEBUG: generating subplan XXX_1 for subquery SELECT min(key) AS min 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)) select_data WHERE (key OPERATOR(pg_catalog.>) 1) +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM intermediate_result_pruning.table_2 WHERE (key OPERATOR(pg_catalog.>=) (SELECT intermediate_result.min FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(min integer))) RETURNING key, value +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, value 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)) raw_data +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + key | value +--------------------------------------------------------------------- + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 +(4 rows) + +ROLLBACK; +-- select_data goes to a single node, because it is used in another subquery +-- raw_data is also the final router query, so hits a single shard +-- however, the subquery in WHERE clause of the DELETE query is broadcasted to all +-- nodes +BEGIN; +WITH select_data AS MATERIALIZED ( + SELECT * FROM table_1 +), +raw_data AS MATERIALIZED ( + DELETE FROM table_2 WHERE value::int >= (SELECT min(key) FROM select_data WHERE key > 1 + random()) RETURNING * +) +SELECT * FROM raw_data; +DEBUG: generating subplan XXX_1 for CTE select_data: SELECT key, value FROM intermediate_result_pruning.table_1 +DEBUG: generating subplan XXX_2 for CTE raw_data: DELETE FROM intermediate_result_pruning.table_2 WHERE ((value)::integer OPERATOR(pg_catalog.>=) (SELECT min(select_data.key) AS min 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)) select_data WHERE ((select_data.key)::double precision OPERATOR(pg_catalog.>) ((1)::double precision OPERATOR(pg_catalog.+) random())))) RETURNING key, value +DEBUG: generating subplan XXX_1 for subquery SELECT min(key) AS min 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)) select_data WHERE ((key)::double precision OPERATOR(pg_catalog.>) ((1)::double precision OPERATOR(pg_catalog.+) random())) +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM intermediate_result_pruning.table_2 WHERE ((value)::integer OPERATOR(pg_catalog.>=) (SELECT intermediate_result.min FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(min integer))) RETURNING key, value +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, value 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)) raw_data +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + key | value +--------------------------------------------------------------------- + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 +(4 rows) + +ROLLBACK; +-- now, we need only two intermediate results as the subquery in WHERE clause is +-- router plannable +BEGIN; +WITH select_data AS MATERIALIZED ( + SELECT * FROM table_1 +), +raw_data AS MATERIALIZED ( + DELETE FROM table_2 WHERE value::int >= (SELECT min(key) FROM table_1 WHERE key > random()) AND key = 6 RETURNING * +) +SELECT * FROM raw_data; +DEBUG: generating subplan XXX_1 for CTE raw_data: DELETE FROM intermediate_result_pruning.table_2 WHERE (((value)::integer OPERATOR(pg_catalog.>=) (SELECT min(table_1.key) AS min FROM intermediate_result_pruning.table_1 WHERE ((table_1.key)::double precision OPERATOR(pg_catalog.>) random()))) AND (key OPERATOR(pg_catalog.=) 6)) RETURNING key, value +DEBUG: generating subplan XXX_1 for subquery SELECT min(key) AS min FROM intermediate_result_pruning.table_1 WHERE ((key)::double precision OPERATOR(pg_catalog.>) random()) +DEBUG: Plan XXX query after replacing subqueries and CTEs: DELETE FROM intermediate_result_pruning.table_2 WHERE (((value)::integer OPERATOR(pg_catalog.>=) (SELECT intermediate_result.min FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(min integer))) AND (key OPERATOR(pg_catalog.=) 6)) RETURNING key, value +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, 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)) raw_data +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + key | value +--------------------------------------------------------------------- + 6 | 6 +(1 row) + +ROLLBACK; +-- test with INSERT SELECT via coordinator +-- INSERT .. SELECT via coordinator that doesn't have any intermediate results +-- 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: 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 +-- single shard via filter in key +INSERT INTO table_1 + SELECT * FROM table_2 where value IN (SELECT value FROM table_1 WHERE random() > 1) AND key = 1; +DEBUG: volatile functions are not allowed in distributed INSERT ... SELECT queries +DEBUG: generating subplan XXX_1 for subquery SELECT value FROM intermediate_result_pruning.table_1 WHERE (random() OPERATOR(pg_catalog.>) (1)::double precision) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, value FROM intermediate_result_pruning.table_2 WHERE ((value OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(value text))) AND (key OPERATOR(pg_catalog.=) 1)) +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +-- a similar query, with more complex subquery +INSERT INTO table_1 + SELECT * FROM table_2 where key = 1 AND + value::int IN + (WITH cte_1 AS MATERIALIZED + ( + (SELECT key FROM table_1 WHERE key = 1) + INTERSECT + (SELECT key FROM table_1 WHERE key = 2) + ), + cte_2 AS MATERIALIZED + ( + (SELECT key FROM table_1 WHERE key = 3) + INTERSECT + (SELECT key FROM table_1 WHERE key = 4) + ) + SELECT * FROM cte_1 + UNION + SELECT * FROM cte_2); +DEBUG: Set operations are not allowed in distributed INSERT ... SELECT queries +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) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer) INTERSECT SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer) +DEBUG: generating subplan XXX_2 for CTE cte_2: SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 3) INTERSECT SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 4) +DEBUG: generating subplan XXX_3 for subquery SELECT cte_1.key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_1 UNION SELECT cte_2.key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT key, value FROM intermediate_result_pruning.table_2 WHERE ((key OPERATOR(pg_catalog.=) 1) AND ((value)::integer OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.key FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(key integer)))) +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx +-- same query, cte is on the FROM clause +-- and this time the final query (and top-level intermediate result) +-- hits all the shards because table_2.key != 1 +INSERT INTO table_1 + SELECT table_2.* FROM table_2, + (WITH cte_1 AS MATERIALIZED + ( + (SELECT key FROM table_1 WHERE key = 1) + INTERSECT + (SELECT key FROM table_1 WHERE key = 2) + ), + cte_2 AS MATERIALIZED + ( + (SELECT key FROM table_1 WHERE key = 3) + INTERSECT + (SELECT key FROM table_1 WHERE key = 4) + ) + SELECT * FROM cte_1 + UNION + SELECT * FROM cte_2 + ) 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: 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) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer) INTERSECT SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer) +DEBUG: generating subplan XXX_2 for CTE cte_2: SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 3) INTERSECT SELECT table_1.key FROM intermediate_result_pruning.table_1 WHERE (table_1.key OPERATOR(pg_catalog.=) 4) +DEBUG: generating subplan XXX_3 for subquery SELECT cte_1.key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_1 UNION SELECT cte_2.key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) cte_2 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table_2.key, table_2.value FROM intermediate_result_pruning.table_2, (SELECT intermediate_result.key FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) foo WHERE ((table_2.key OPERATOR(pg_catalog.<>) 1) AND (foo.key OPERATOR(pg_catalog.=) (table_2.value)::integer)) +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_3 will be sent to localhost:xxxxx +-- append partitioned/heap-type +-- do not print out 'building index pg_toast_xxxxx_index' messages +SET client_min_messages TO DEFAULT; +CREATE TABLE range_partitioned(range_column text, data int); +SET client_min_messages TO DEBUG1; +SELECT create_distributed_table('range_partitioned', 'range_column', 'range'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT master_create_empty_shard('range_partitioned'); + master_create_empty_shard +--------------------------------------------------------------------- + 1480013 +(1 row) + +SELECT master_create_empty_shard('range_partitioned'); + master_create_empty_shard +--------------------------------------------------------------------- + 1480014 +(1 row) + +SELECT master_create_empty_shard('range_partitioned'); + master_create_empty_shard +--------------------------------------------------------------------- + 1480015 +(1 row) + +SELECT master_create_empty_shard('range_partitioned'); + master_create_empty_shard +--------------------------------------------------------------------- + 1480016 +(1 row) + +SELECT master_create_empty_shard('range_partitioned'); + master_create_empty_shard +--------------------------------------------------------------------- + 1480017 +(1 row) + +UPDATE pg_dist_shard SET shardminvalue = 'A', shardmaxvalue = 'D' WHERE shardid = 1480013; +UPDATE pg_dist_shard SET shardminvalue = 'D', shardmaxvalue = 'G' WHERE shardid = 1480014; +UPDATE pg_dist_shard SET shardminvalue = 'G', shardmaxvalue = 'K' WHERE shardid = 1480015; +UPDATE pg_dist_shard SET shardminvalue = 'K', shardmaxvalue = 'O' WHERE shardid = 1480016; +UPDATE pg_dist_shard SET shardminvalue = 'O', shardmaxvalue = 'Z' WHERE shardid = 1480017; +-- final query goes to a single shard +SELECT + count(*) +FROM + range_partitioned +WHERE + range_column = 'A' AND + data IN (SELECT data FROM range_partitioned); +DEBUG: generating subplan XXX_1 for subquery SELECT data FROM intermediate_result_pruning.range_partitioned +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM intermediate_result_pruning.range_partitioned WHERE ((range_column OPERATOR(pg_catalog.=) 'A'::text) AND (data OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.data FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(data integer)))) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- final query goes to three shards, so multiple workers +SELECT + count(*) +FROM + range_partitioned +WHERE + range_column >= 'A' AND range_column <= 'K' AND + data IN (SELECT data FROM range_partitioned); +DEBUG: generating subplan XXX_1 for subquery SELECT data FROM intermediate_result_pruning.range_partitioned +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM intermediate_result_pruning.range_partitioned WHERE ((range_column OPERATOR(pg_catalog.>=) 'A'::text) AND (range_column OPERATOR(pg_catalog.<=) 'K'::text) AND (data OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.data FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(data integer)))) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- two shards, both of which are on the first node +WITH some_data AS ( + SELECT data FROM range_partitioned +) +SELECT + count(*) +FROM + range_partitioned +WHERE + range_column IN ('A', 'E') AND + range_partitioned.data IN (SELECT data FROM some_data); +DEBUG: CTE some_data is going to be inlined via distributed planning +DEBUG: generating subplan XXX_1 for subquery SELECT data FROM (SELECT range_partitioned.data FROM intermediate_result_pruning.range_partitioned) some_data +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM intermediate_result_pruning.range_partitioned WHERE ((range_column OPERATOR(pg_catalog.=) ANY (ARRAY['A'::text, 'E'::text])) AND (data OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.data FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(data integer)))) +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- test case for issue #3556 +CREATE TABLE accounts (id text PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "accounts_pkey" for table "accounts" +CREATE TABLE stats (account_id text PRIMARY KEY, spent int); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "stats_pkey" for table "stats" +SELECT create_distributed_table('accounts', 'id', colocate_with => 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('stats', 'account_id', colocate_with => 'accounts'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO accounts (id) VALUES ('foo'); +INSERT INTO stats (account_id, spent) VALUES ('foo', 100); +SELECT * +FROM +( + WITH accounts_cte AS MATERIALIZED ( + SELECT id AS account_id + FROM accounts + ), + joined_stats_cte_1 AS MATERIALIZED ( + SELECT spent, account_id + FROM stats + INNER JOIN accounts_cte USING (account_id) + ), + joined_stats_cte_2 AS MATERIALIZED ( + SELECT spent, account_id + FROM joined_stats_cte_1 + INNER JOIN accounts_cte USING (account_id) + ) + SELECT SUM(spent) OVER (PARTITION BY coalesce(account_id, NULL)) + FROM accounts_cte + INNER JOIN joined_stats_cte_2 USING (account_id) +) inner_query; +DEBUG: generating subplan XXX_1 for CTE accounts_cte: SELECT id AS account_id FROM intermediate_result_pruning.accounts +DEBUG: generating subplan XXX_2 for CTE joined_stats_cte_1: SELECT stats.spent, stats.account_id FROM (intermediate_result_pruning.stats JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte USING (account_id)) +DEBUG: generating subplan XXX_3 for CTE joined_stats_cte_2: SELECT joined_stats_cte_1.spent, joined_stats_cte_1.account_id FROM ((SELECT intermediate_result.spent, intermediate_result.account_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(spent integer, account_id text)) joined_stats_cte_1 JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte USING (account_id)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum FROM (SELECT sum(joined_stats_cte_2.spent) OVER (PARTITION BY COALESCE(accounts_cte.account_id, NULL::text)) AS sum FROM ((SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte JOIN (SELECT intermediate_result.spent, intermediate_result.account_id FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(spent integer, account_id text)) joined_stats_cte_2 USING (account_id))) inner_query +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Subplan XXX_3 will be written to local file + sum +--------------------------------------------------------------------- + 100 +(1 row) + +-- confirm that the pruning works well when using round-robin as well +SET citus.task_assignment_policy to 'round-robin'; +SELECT * +FROM +( + WITH accounts_cte AS MATERIALIZED ( + SELECT id AS account_id + FROM accounts + ), + joined_stats_cte_1 AS MATERIALIZED ( + SELECT spent, account_id + FROM stats + INNER JOIN accounts_cte USING (account_id) + ), + joined_stats_cte_2 AS MATERIALIZED ( + SELECT spent, account_id + FROM joined_stats_cte_1 + INNER JOIN accounts_cte USING (account_id) + ) + SELECT SUM(spent) OVER (PARTITION BY coalesce(account_id, NULL)) + FROM accounts_cte + INNER JOIN joined_stats_cte_2 USING (account_id) +) inner_query; +DEBUG: generating subplan XXX_1 for CTE accounts_cte: SELECT id AS account_id FROM intermediate_result_pruning.accounts +DEBUG: generating subplan XXX_2 for CTE joined_stats_cte_1: SELECT stats.spent, stats.account_id FROM (intermediate_result_pruning.stats JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte USING (account_id)) +DEBUG: generating subplan XXX_3 for CTE joined_stats_cte_2: SELECT joined_stats_cte_1.spent, joined_stats_cte_1.account_id FROM ((SELECT intermediate_result.spent, intermediate_result.account_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(spent integer, account_id text)) joined_stats_cte_1 JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte USING (account_id)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum FROM (SELECT sum(joined_stats_cte_2.spent) OVER (PARTITION BY COALESCE(accounts_cte.account_id, NULL::text)) AS sum FROM ((SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte JOIN (SELECT intermediate_result.spent, intermediate_result.account_id FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(spent integer, account_id text)) joined_stats_cte_2 USING (account_id))) inner_query +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 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 + sum +--------------------------------------------------------------------- + 100 +(1 row) + +RESET citus.task_assignment_policy; +-- Insert..select is planned differently, make sure we have results everywhere. +-- We put the insert..select in a CTE here to prevent the CTE from being moved +-- into the select, which would follow the regular code path for select. +WITH stats AS MATERIALIZED ( + SELECT count(key) m FROM table_3 +), +inserts AS MATERIALIZED ( + INSERT INTO table_2 + SELECT key, count(*) + FROM table_1 + WHERE key > (SELECT m FROM stats) + GROUP BY key + HAVING count(*) < (SELECT m FROM stats) + LIMIT 1 + RETURNING * +) 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: 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 +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Collecting INSERT ... SELECT results on coordinator + count +--------------------------------------------------------------------- + 1 +(1 row) + +SET citus.task_assignment_policy to DEFAULT; +SET client_min_messages TO DEFAULT; +DROP TABLE table_1, table_2, table_3, ref_table, accounts, stats, range_partitioned; +DROP SCHEMA intermediate_result_pruning; diff --git a/src/test/regress/expected/isolation_concurrent_move_create_table.out b/src/test/regress/expected/isolation_concurrent_move_create_table.out index 4ee46db32..14b31ec0e 100644 --- a/src/test/regress/expected/isolation_concurrent_move_create_table.out +++ b/src/test/regress/expected/isolation_concurrent_move_create_table.out @@ -2,10 +2,10 @@ Parsed test spec with 5 sessions starting permutation: s2-begin s2-create_distributed_table s3-create_distributed_table s2-commit step s2-begin: - BEGIN; + BEGIN; step s2-create_distributed_table: - SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); + SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); create_distributed_table --------------------------------------------------------------------- @@ -13,7 +13,7 @@ create_distributed_table (1 row) step s3-create_distributed_table: - SELECT create_distributed_table('concurrent_table_3', 'id', colocate_with := 'concurrent_table_1'); + SELECT create_distributed_table('concurrent_table_3', 'id', colocate_with := 'concurrent_table_1'); create_distributed_table --------------------------------------------------------------------- @@ -21,15 +21,15 @@ create_distributed_table (1 row) step s2-commit: - COMMIT; + COMMIT; starting permutation: s2-begin s2-create_distributed_table s1-move-shard-logical s2-commit s3-sanity-check s3-sanity-check-2 step s2-begin: - BEGIN; + BEGIN; step s2-create_distributed_table: - SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); + SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); create_distributed_table --------------------------------------------------------------------- @@ -37,15 +37,15 @@ create_distributed_table (1 row) step s1-move-shard-logical: - WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_1'::regclass ORDER BY shardid LIMIT 1) - SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638) FROM shardid; + WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_1'::regclass ORDER BY shardid LIMIT 1) + SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638) FROM shardid; ERROR: could not acquire the lock required to move public.concurrent_table_1 step s2-commit: - COMMIT; + COMMIT; step s3-sanity-check: - SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; + SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; count --------------------------------------------------------------------- @@ -53,7 +53,7 @@ count (1 row) step s3-sanity-check-2: - SELECT count(*) FROM concurrent_table_1 JOIN concurrent_table_2 USING (id); + SELECT count(*) FROM concurrent_table_1 JOIN concurrent_table_2 USING (id); count --------------------------------------------------------------------- @@ -63,10 +63,10 @@ count starting permutation: s2-begin s2-create_distributed_table s1-move-shard-block s2-commit s3-sanity-check s3-sanity-check-2 step s2-begin: - BEGIN; + BEGIN; step s2-create_distributed_table: - SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); + SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); create_distributed_table --------------------------------------------------------------------- @@ -74,15 +74,15 @@ create_distributed_table (1 row) step s1-move-shard-block: - WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_1'::regclass ORDER BY shardid LIMIT 1) - SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638, 'block_writes') FROM shardid; + WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_1'::regclass ORDER BY shardid LIMIT 1) + SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638, 'block_writes') FROM shardid; ERROR: could not acquire the lock required to move public.concurrent_table_1 step s2-commit: - COMMIT; + COMMIT; step s3-sanity-check: - SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; + SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; count --------------------------------------------------------------------- @@ -90,7 +90,7 @@ count (1 row) step s3-sanity-check-2: - SELECT count(*) FROM concurrent_table_1 JOIN concurrent_table_2 USING (id); + SELECT count(*) FROM concurrent_table_1 JOIN concurrent_table_2 USING (id); count --------------------------------------------------------------------- @@ -100,10 +100,10 @@ count starting permutation: s2-begin s2-create_distributed_table s1-split-block s2-commit s3-sanity-check s3-sanity-check-2 step s2-begin: - BEGIN; + BEGIN; step s2-create_distributed_table: - SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); + SELECT create_distributed_table('concurrent_table_2', 'id', colocate_with := 'concurrent_table_1'); create_distributed_table --------------------------------------------------------------------- @@ -111,16 +111,16 @@ create_distributed_table (1 row) step s1-split-block: - WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_1'::regclass ORDER BY shardid LIMIT 1) - SELECT citus_split_shard_by_split_points( - shardid.shardid, ARRAY['2113265921'], ARRAY[(SELECT * FROM first_node_id), (SELECT * FROM first_node_id)], 'block_writes') FROM shardid; + WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_1'::regclass ORDER BY shardid LIMIT 1) + SELECT citus_split_shard_by_split_points( + shardid.shardid, ARRAY['2113265921'], ARRAY[(SELECT * FROM first_node_id), (SELECT * FROM first_node_id)], 'block_writes') FROM shardid; ERROR: could not acquire the lock required to split public.concurrent_table_1 step s2-commit: - COMMIT; + COMMIT; step s3-sanity-check: - SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; + SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; count --------------------------------------------------------------------- @@ -128,7 +128,7 @@ count (1 row) step s3-sanity-check-2: - SELECT count(*) FROM concurrent_table_1 JOIN concurrent_table_2 USING (id); + SELECT count(*) FROM concurrent_table_1 JOIN concurrent_table_2 USING (id); count --------------------------------------------------------------------- @@ -138,11 +138,11 @@ count starting permutation: s4-begin s4-move-shard-logical s5-setup-rep-factor s5-create_implicit_colocated_distributed_table s4-commit s3-sanity-check s3-sanity-check-3 s3-sanity-check-4 step s4-begin: - BEGIN; + BEGIN; step s4-move-shard-logical: - WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_4'::regclass ORDER BY shardid LIMIT 1) - SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638) FROM shardid; + WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_4'::regclass ORDER BY shardid LIMIT 1) + SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638) FROM shardid; citus_move_shard_placement --------------------------------------------------------------------- @@ -150,17 +150,17 @@ citus_move_shard_placement (1 row) step s5-setup-rep-factor: - SET citus.shard_replication_factor TO 1; + SET citus.shard_replication_factor TO 1; step s5-create_implicit_colocated_distributed_table: - SELECT create_distributed_table('concurrent_table_5', 'id'); + SELECT create_distributed_table('concurrent_table_5', 'id'); -ERROR: could not acquire the lock required to colocate distributed table public.concurrent_table_4 +ERROR: could not acquire the lock required to colocate distributed table public.concurrent_table_1 step s4-commit: - commit; + commit; step s3-sanity-check: - SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; + SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; count --------------------------------------------------------------------- @@ -168,7 +168,7 @@ count (1 row) step s3-sanity-check-3: - SELECT count(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid IN ('concurrent_table_4', 'concurrent_table_5'); + SELECT count(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid IN ('concurrent_table_4', 'concurrent_table_5'); count --------------------------------------------------------------------- @@ -176,7 +176,7 @@ count (1 row) step s3-sanity-check-4: - SELECT count(*) FROM concurrent_table_4 JOIN concurrent_table_5 USING (id); + SELECT count(*) FROM concurrent_table_4 JOIN concurrent_table_5 USING (id); count --------------------------------------------------------------------- @@ -186,11 +186,11 @@ count starting permutation: s4-begin s4-move-shard-block s5-setup-rep-factor s5-create_implicit_colocated_distributed_table s4-commit s3-sanity-check s3-sanity-check-3 s3-sanity-check-4 step s4-begin: - BEGIN; + BEGIN; step s4-move-shard-block: - WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_4'::regclass ORDER BY shardid LIMIT 1) - SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638, 'block_writes') FROM shardid; + WITH shardid AS (SELECT shardid FROM pg_dist_shard where logicalrelid = 'concurrent_table_4'::regclass ORDER BY shardid LIMIT 1) + SELECT citus_move_Shard_placement(shardid.shardid, 'localhost', 57637, 'localhost', 57638, 'block_writes') FROM shardid; citus_move_shard_placement --------------------------------------------------------------------- @@ -198,17 +198,17 @@ citus_move_shard_placement (1 row) step s5-setup-rep-factor: - SET citus.shard_replication_factor TO 1; + SET citus.shard_replication_factor TO 1; step s5-create_implicit_colocated_distributed_table: - SELECT create_distributed_table('concurrent_table_5', 'id'); + SELECT create_distributed_table('concurrent_table_5', 'id'); -ERROR: could not acquire the lock required to colocate distributed table public.concurrent_table_4 +ERROR: could not acquire the lock required to colocate distributed table public.concurrent_table_1 step s4-commit: - commit; + commit; step s3-sanity-check: - SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; + SELECT count(*) FROM pg_dist_shard LEFT JOIN pg_dist_shard_placement USING(shardid) WHERE nodename IS NULL; count --------------------------------------------------------------------- @@ -216,7 +216,7 @@ count (1 row) step s3-sanity-check-3: - SELECT count(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid IN ('concurrent_table_4', 'concurrent_table_5'); + SELECT count(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid IN ('concurrent_table_4', 'concurrent_table_5'); count --------------------------------------------------------------------- @@ -224,7 +224,7 @@ count (1 row) step s3-sanity-check-4: - SELECT count(*) FROM concurrent_table_4 JOIN concurrent_table_5 USING (id); + SELECT count(*) FROM concurrent_table_4 JOIN concurrent_table_5 USING (id); count --------------------------------------------------------------------- diff --git a/src/test/regress/expected/isolation_create_distributed_table_concurrently.out b/src/test/regress/expected/isolation_create_distributed_table_concurrently.out new file mode 100644 index 000000000..bf092851e --- /dev/null +++ b/src/test/regress/expected/isolation_create_distributed_table_concurrently.out @@ -0,0 +1,763 @@ +unused step name: s1-create-concurrently-table_2 +Parsed test spec with 4 sessions + +starting permutation: s1-truncate s3-acquire-split-advisory-lock s1-settings s2-settings s1-create-concurrently-table_1 s2-begin s2-insert s2-commit s3-release-split-advisory-lock s2-print-status +step s1-truncate: + TRUNCATE table_1; + +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-settings: + -- session needs to have replication factor set to 1, can't do in setup + SET citus.shard_count TO 4; + SET citus.shard_replication_factor TO 1; + +step s2-settings: + -- session needs to have replication factor set to 1, can't do in setup + SET citus.shard_count TO 4; + SET citus.shard_replication_factor TO 1; + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-begin: + BEGIN; + +step s2-insert: + INSERT INTO table_1 SELECT s FROM generate_series(1,20) s; + +step s2-commit: + COMMIT; + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-print-status: + -- sanity check on partitions + SELECT * FROM pg_dist_shard + WHERE logicalrelid = 'table_1'::regclass OR logicalrelid = 'table_2'::regclass + ORDER BY shardminvalue::BIGINT, logicalrelid; + -- sanity check on total elements in the table + SELECT COUNT(*) FROM table_1; + +logicalrelid|shardid|shardstorage|shardminvalue|shardmaxvalue +--------------------------------------------------------------------- +table_1 |1400294|t | -2147483648| -1073741825 +table_1 |1400295|t | -1073741824| -1 +table_1 |1400296|t | 0| 1073741823 +table_1 |1400297|t | 1073741824| 2147483647 +(4 rows) + +count +--------------------------------------------------------------------- + 20 +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-truncate s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-begin s2-insert s2-update s2-commit s3-release-split-advisory-lock s2-print-status +step s1-truncate: + TRUNCATE table_1; + +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-begin: + BEGIN; + +step s2-insert: + INSERT INTO table_1 SELECT s FROM generate_series(1,20) s; + +step s2-update: + UPDATE table_1 SET id = 21 WHERE id = 20; + +step s2-commit: + COMMIT; + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-print-status: + -- sanity check on partitions + SELECT * FROM pg_dist_shard + WHERE logicalrelid = 'table_1'::regclass OR logicalrelid = 'table_2'::regclass + ORDER BY shardminvalue::BIGINT, logicalrelid; + -- sanity check on total elements in the table + SELECT COUNT(*) FROM table_1; + +logicalrelid|shardid|shardstorage|shardminvalue|shardmaxvalue +--------------------------------------------------------------------- +table_1 |1400299|t | -2147483648| -1073741825 +table_1 |1400300|t | -1073741824| -1 +table_1 |1400301|t | 0| 1073741823 +table_1 |1400302|t | 1073741824| 2147483647 +(4 rows) + +count +--------------------------------------------------------------------- + 20 +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-truncate s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-begin s2-insert s2-delete s2-commit s3-release-split-advisory-lock s2-print-status +step s1-truncate: + TRUNCATE table_1; + +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-begin: + BEGIN; + +step s2-insert: + INSERT INTO table_1 SELECT s FROM generate_series(1,20) s; + +step s2-delete: + DELETE FROM table_1 WHERE id = 11; + +step s2-commit: + COMMIT; + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-print-status: + -- sanity check on partitions + SELECT * FROM pg_dist_shard + WHERE logicalrelid = 'table_1'::regclass OR logicalrelid = 'table_2'::regclass + ORDER BY shardminvalue::BIGINT, logicalrelid; + -- sanity check on total elements in the table + SELECT COUNT(*) FROM table_1; + +logicalrelid|shardid|shardstorage|shardminvalue|shardmaxvalue +--------------------------------------------------------------------- +table_1 |1400304|t | -2147483648| -1073741825 +table_1 |1400305|t | -1073741824| -1 +table_1 |1400306|t | 0| 1073741823 +table_1 |1400307|t | 1073741824| 2147483647 +(4 rows) + +count +--------------------------------------------------------------------- + 19 +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-truncate s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-begin s2-insert s2-copy s2-commit s3-release-split-advisory-lock s2-print-status +step s1-truncate: + TRUNCATE table_1; + +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-begin: + BEGIN; + +step s2-insert: + INSERT INTO table_1 SELECT s FROM generate_series(1,20) s; + +step s2-copy: + COPY table_1 FROM PROGRAM 'echo 30 && echo 31 && echo 32 && echo 33 && echo 34 && echo 35 && echo 36 && echo 37 && echo 38'; + +step s2-commit: + COMMIT; + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-print-status: + -- sanity check on partitions + SELECT * FROM pg_dist_shard + WHERE logicalrelid = 'table_1'::regclass OR logicalrelid = 'table_2'::regclass + ORDER BY shardminvalue::BIGINT, logicalrelid; + -- sanity check on total elements in the table + SELECT COUNT(*) FROM table_1; + +logicalrelid|shardid|shardstorage|shardminvalue|shardmaxvalue +--------------------------------------------------------------------- +table_1 |1400309|t | -2147483648| -1073741825 +table_1 |1400310|t | -1073741824| -1 +table_1 |1400311|t | 0| 1073741823 +table_1 |1400312|t | 1073741824| 2147483647 +(4 rows) + +count +--------------------------------------------------------------------- + 29 +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-insert s2-reindex-concurrently s4-print-waiting-locks s3-release-split-advisory-lock +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-insert: + INSERT INTO table_1 SELECT s FROM generate_series(1,20) s; + +step s2-reindex-concurrently: + REINDEX TABLE CONCURRENTLY table_1; + +step s4-print-waiting-locks: + SELECT mode, relation::regclass, granted FROM pg_locks + WHERE relation = 'table_1'::regclass OR relation = 'table_2'::regclass + ORDER BY mode, relation, granted; + +mode |relation|granted +--------------------------------------------------------------------- +AccessShareLock |table_1 |t +ShareUpdateExclusiveLock|table_1 |f +ShareUpdateExclusiveLock|table_1 |t +(3 rows) + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-reindex-concurrently: <... completed> +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-insert s2-reindex s4-print-waiting-locks s3-release-split-advisory-lock +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-insert: + INSERT INTO table_1 SELECT s FROM generate_series(1,20) s; + +step s2-reindex: + REINDEX TABLE table_1; + +step s4-print-waiting-locks: + SELECT mode, relation::regclass, granted FROM pg_locks + WHERE relation = 'table_1'::regclass OR relation = 'table_2'::regclass + ORDER BY mode, relation, granted; + +mode |relation|granted +--------------------------------------------------------------------- +AccessExclusiveLock |table_1 |f +AccessShareLock |table_1 |t +ShareUpdateExclusiveLock|table_1 |t +(3 rows) + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-reindex: <... completed> +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-begin s2-create-concurrently-table_1 s2-commit +step s2-begin: + BEGIN; + +step s2-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +ERROR: create_distributed_table_concurrently cannot run inside a transaction block +step s2-commit: + COMMIT; + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-create-concurrently-table_1 s3-release-split-advisory-lock +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +ERROR: another create_distributed_table_concurrently operation is in progress +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-create-concurrently-table_2 s3-release-split-advisory-lock +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-create-concurrently-table_2: + SELECT create_distributed_table_concurrently('table_2', 'id'); + +ERROR: another create_distributed_table_concurrently operation is in progress +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-create-table_1 s3-release-split-advisory-lock +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-create-table_1: + SELECT create_distributed_table('table_1', 'id'); + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-create-table_1: <... completed> +ERROR: table "table_1" is already distributed +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-create-table_2 s3-release-split-advisory-lock +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-create-table_2: + SELECT create_distributed_table('table_2', 'id'); + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s2-create-table_2: <... completed> +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-begin s2-create-table_2 s1-create-concurrently-table_default_colocated s4-print-waiting-advisory-locks s2-commit s4-print-colocations +step s2-begin: + BEGIN; + +step s2-create-table_2: + SELECT create_distributed_table('table_2', 'id'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_default_colocated: + SELECT create_distributed_table_concurrently('table_default_colocated', 'id'); + +step s4-print-waiting-advisory-locks: + SELECT mode, classid, objid, objsubid, granted FROM pg_locks + WHERE locktype = 'advisory' AND classid = 0 AND objid = 3 AND objsubid = 9 + ORDER BY granted; + +mode |classid|objid|objsubid|granted +--------------------------------------------------------------------- +ExclusiveLock| 0| 3| 9|f +ExclusiveLock| 0| 3| 9|t +(2 rows) + +step s2-commit: + COMMIT; + +step s1-create-concurrently-table_default_colocated: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s4-print-colocations: + SELECT * FROM pg_dist_colocation ORDER BY colocationid; + +colocationid|shardcount|replicationfactor|distributioncolumntype|distributioncolumncollation +--------------------------------------------------------------------- + 123173| 4| 1| 21| 0 + 123174| 4| 1| 23| 0 +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-create-concurrently-table_default_colocated s3-acquire-split-advisory-lock s1-create-concurrently-table_1 s2-create-table_2 s4-print-waiting-advisory-locks s3-release-split-advisory-lock s4-print-colocations +step s1-create-concurrently-table_default_colocated: + SELECT create_distributed_table_concurrently('table_default_colocated', 'id'); + +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s3-acquire-split-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_1: + SELECT create_distributed_table_concurrently('table_1', 'id'); + +step s2-create-table_2: + SELECT create_distributed_table('table_2', 'id'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s4-print-waiting-advisory-locks: + SELECT mode, classid, objid, objsubid, granted FROM pg_locks + WHERE locktype = 'advisory' AND classid = 0 AND objid = 3 AND objsubid = 9 + ORDER BY granted; + +mode|classid|objid|objsubid|granted +--------------------------------------------------------------------- +(0 rows) + +step s3-release-split-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-create-concurrently-table_1: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s4-print-colocations: + SELECT * FROM pg_dist_colocation ORDER BY colocationid; + +colocationid|shardcount|replicationfactor|distributioncolumntype|distributioncolumncollation +--------------------------------------------------------------------- + 123175| 4| 1| 23| 0 + 123176| 4| 1| 21| 0 +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-begin s2-create-table_2 s1-create-concurrently-table_none_colocated s4-print-waiting-advisory-locks s2-commit s4-print-colocations +step s2-begin: + BEGIN; + +step s2-create-table_2: + SELECT create_distributed_table('table_2', 'id'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_none_colocated: + SELECT create_distributed_table_concurrently('table_none_colocated', 'id', colocate_with => 'none'); + +step s4-print-waiting-advisory-locks: + SELECT mode, classid, objid, objsubid, granted FROM pg_locks + WHERE locktype = 'advisory' AND classid = 0 AND objid = 3 AND objsubid = 9 + ORDER BY granted; + +mode |classid|objid|objsubid|granted +--------------------------------------------------------------------- +ExclusiveLock| 0| 3| 9|t +(1 row) + +step s2-commit: + COMMIT; + +step s1-create-concurrently-table_none_colocated: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s4-print-colocations: + SELECT * FROM pg_dist_colocation ORDER BY colocationid; + +colocationid|shardcount|replicationfactor|distributioncolumntype|distributioncolumncollation +--------------------------------------------------------------------- + 123177| 4| 1| 21| 0 + 123178| 4| 1| 23| 0 +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-begin s2-create-table_2-none s1-create-concurrently-table_none_colocated s4-print-waiting-advisory-locks s2-commit s4-print-colocations +step s2-begin: + BEGIN; + +step s2-create-table_2-none: + SELECT create_distributed_table('table_2', 'id', colocate_with => 'none'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s1-create-concurrently-table_none_colocated: + SELECT create_distributed_table_concurrently('table_none_colocated', 'id', colocate_with => 'none'); + +step s4-print-waiting-advisory-locks: + SELECT mode, classid, objid, objsubid, granted FROM pg_locks + WHERE locktype = 'advisory' AND classid = 0 AND objid = 3 AND objsubid = 9 + ORDER BY granted; + +mode|classid|objid|objsubid|granted +--------------------------------------------------------------------- +(0 rows) + +step s2-commit: + COMMIT; + +step s1-create-concurrently-table_none_colocated: <... completed> +create_distributed_table_concurrently +--------------------------------------------------------------------- + +(1 row) + +step s4-print-colocations: + SELECT * FROM pg_dist_colocation ORDER BY colocationid; + +colocationid|shardcount|replicationfactor|distributioncolumntype|distributioncolumncollation +--------------------------------------------------------------------- + 123179| 4| 1| 21| 0 + 123180| 4| 1| 23| 0 +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/isolation_distributed_deadlock_detection.out b/src/test/regress/expected/isolation_distributed_deadlock_detection.out index 087207bf4..e8bc44999 100644 --- a/src/test/regress/expected/isolation_distributed_deadlock_detection.out +++ b/src/test/regress/expected/isolation_distributed_deadlock_detection.out @@ -90,7 +90,7 @@ step s2-commit: COMMIT; -starting permutation: s1-begin s2-begin s1-update-1 s2-update-2 s1-update-2 deadlock-checker-call s2-upsert-select-all deadlock-checker-call s1-commit s2-commit +starting permutation: s1-begin s2-begin s1-update-1 s2-update-2 s1-update-2 deadlock-checker-call s2-upsert-select-all deadlock-checker-call s2-commit s1-commit step s1-begin: BEGIN; @@ -128,14 +128,14 @@ t step s1-update-2: <... completed> step s2-upsert-select-all: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock -step s1-commit: - COMMIT; - step s2-commit: COMMIT; +step s1-commit: + COMMIT; -starting permutation: s1-begin s2-begin s1-update-1 s2-update-2 s1-update-2 deadlock-checker-call s2-ddl deadlock-checker-call s1-commit s2-commit + +starting permutation: s1-begin s2-begin s1-update-1 s2-update-2 s1-update-2 deadlock-checker-call s2-ddl deadlock-checker-call s2-commit s1-commit step s1-begin: BEGIN; @@ -173,10 +173,10 @@ t step s1-update-2: <... completed> step s2-ddl: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock -step s1-commit: +step s2-commit: COMMIT; -step s2-commit: +step s1-commit: COMMIT; @@ -217,7 +217,7 @@ step s2-commit: COMMIT; -starting permutation: s1-begin s2-begin s2-insert-ref-10 s1-insert-ref-11 s1-insert-ref-10 s2-insert-ref-11 deadlock-checker-call s1-commit s2-commit +starting permutation: s1-begin s2-begin s2-insert-ref-10 s1-insert-ref-11 s1-insert-ref-10 s2-insert-ref-11 deadlock-checker-call s2-commit s1-commit step s1-begin: BEGIN; @@ -247,10 +247,10 @@ t step s1-insert-ref-10: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock step s2-insert-ref-11: <... completed> -step s1-commit: +step s2-commit: COMMIT; -step s2-commit: +step s1-commit: COMMIT; @@ -357,7 +357,7 @@ step s1-commit: COMMIT; -starting permutation: s1-begin s2-begin s3-begin s2-update-1 s1-update-1 s2-update-2 s3-update-3 s3-update-2 deadlock-checker-call s2-update-3 deadlock-checker-call s3-commit s2-commit s1-commit +starting permutation: s1-begin s2-begin s3-begin s2-update-1 s1-update-1 s2-update-2 s3-update-3 s3-update-2 deadlock-checker-call s2-update-3 deadlock-checker-call s2-commit s3-commit s1-commit step s1-begin: BEGIN; @@ -404,18 +404,18 @@ t step s3-update-2: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock step s2-update-3: <... completed> -step s3-commit: - COMMIT; - step s2-commit: COMMIT; step s1-update-1: <... completed> +step s3-commit: + COMMIT; + step s1-commit: COMMIT; -starting permutation: s1-begin s2-begin s3-begin s4-begin s1-update-1 s2-update-2 s3-update-3 s3-update-2 deadlock-checker-call s4-update-4 s2-update-3 deadlock-checker-call s3-commit s2-commit s1-commit s4-commit +starting permutation: s1-begin s2-begin s3-begin s4-begin s1-update-1 s2-update-2 s3-update-3 s3-update-2 deadlock-checker-call s4-update-4 s2-update-3 deadlock-checker-call s2-commit s3-commit s1-commit s4-commit step s1-begin: BEGIN; @@ -465,10 +465,10 @@ t step s3-update-2: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock step s2-update-3: <... completed> -step s3-commit: +step s2-commit: COMMIT; -step s2-commit: +step s3-commit: COMMIT; step s1-commit: @@ -614,7 +614,7 @@ step s3-commit: COMMIT; -starting permutation: s1-begin s2-begin s3-begin s4-begin s5-begin s6-begin s1-update-1 s5-update-5 s3-update-2 s2-update-3 s4-update-4 s3-update-4 deadlock-checker-call s6-update-6 s4-update-6 s1-update-5 s5-update-1 deadlock-checker-call s1-commit s5-commit s6-commit s4-commit s3-commit s2-commit +starting permutation: s1-begin s2-begin s3-begin s4-begin s5-begin s6-begin s1-update-1 s5-update-5 s3-update-2 s2-update-3 s4-update-4 s3-update-4 deadlock-checker-call s6-update-6 s4-update-6 s1-update-5 s5-update-1 deadlock-checker-call s5-commit s1-commit s6-commit s4-commit s3-commit s2-commit step s1-begin: BEGIN; @@ -682,10 +682,10 @@ t step s1-update-5: <... completed> step s5-update-1: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock -step s1-commit: +step s5-commit: COMMIT; -step s5-commit: +step s1-commit: COMMIT; step s6-commit: @@ -703,7 +703,7 @@ step s2-commit: COMMIT; -starting permutation: s1-begin s2-begin s3-begin s4-begin s5-begin s6-begin s6-update-6 s5-update-5 s5-update-6 s4-update-4 s1-update-4 s4-update-5 deadlock-checker-call s2-update-3 s3-update-2 s2-update-2 s3-update-3 deadlock-checker-call s6-commit s5-commit s4-commit s1-commit s3-commit s2-commit +starting permutation: s1-begin s2-begin s3-begin s4-begin s5-begin s6-begin s6-update-6 s5-update-5 s5-update-6 s4-update-4 s1-update-4 s4-update-5 deadlock-checker-call s2-update-3 s3-update-2 s2-update-2 s3-update-3 deadlock-checker-call s3-commit s6-commit s5-commit s4-commit s1-commit s2-commit step s1-begin: BEGIN; @@ -771,6 +771,9 @@ t step s2-update-2: <... completed> step s3-update-3: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock +step s3-commit: + COMMIT; + step s6-commit: COMMIT; @@ -786,17 +789,11 @@ step s1-update-4: <... completed> step s1-commit: COMMIT; -step s3-commit: - COMMIT; - step s2-commit: COMMIT; -starting permutation: s1-begin s2-begin s3-begin s4-begin s5-begin s6-begin s5-update-5 s3-update-2 s2-update-2 s4-update-4 s3-update-4 s4-update-5 s1-update-4 deadlock-checker-call s6-update-6 s5-update-6 s6-update-5 deadlock-checker-call s5-commit s6-commit s4-commit s3-commit s1-commit s2-commit -step s1-begin: - BEGIN; - +starting permutation: s2-begin s3-begin s4-begin s5-begin s6-begin s5-update-5 s3-update-2 s2-update-2 s4-update-4 s3-update-4 s4-update-5 deadlock-checker-call s6-update-6 s5-update-6 s6-update-5 deadlock-checker-call s6-commit s5-commit s4-commit s3-commit s2-commit step s2-begin: BEGIN; @@ -830,9 +827,6 @@ step s3-update-4: step s4-update-5: UPDATE deadlock_detection_test SET some_val = 4 WHERE user_id = 5; -step s1-update-4: - UPDATE deadlock_detection_test SET some_val = 1 WHERE user_id = 4; - step deadlock-checker-call: SELECT check_distributed_deadlocks(); @@ -861,13 +855,13 @@ t step s5-update-6: <... completed> step s6-update-5: <... completed> ERROR: canceling the transaction since it was involved in a distributed deadlock +step s6-commit: + COMMIT; + step s5-commit: COMMIT; step s4-update-5: <... completed> -step s6-commit: - COMMIT; - step s4-commit: COMMIT; @@ -876,10 +870,6 @@ step s3-commit: COMMIT; step s2-update-2: <... completed> -step s1-update-4: <... completed> -step s1-commit: - COMMIT; - step s2-commit: COMMIT; diff --git a/src/test/regress/expected/isolation_drop_vs_all.out b/src/test/regress/expected/isolation_drop_vs_all.out index ec7e4fe24..7dab13615 100644 --- a/src/test/regress/expected/isolation_drop_vs_all.out +++ b/src/test/regress/expected/isolation_drop_vs_all.out @@ -247,7 +247,7 @@ step s1-drop: DROP TABLE drop_hash; step s2-distribute-table: SELECT create_distributed_table('drop_hash', 'id'); step s1-commit: COMMIT; step s2-distribute-table: <... completed> -ERROR: could not create distributed table: relation does not exist +ERROR: relation with OID XXXX does not exist step s2-commit: COMMIT; step s1-select-count: SELECT COUNT(*) FROM drop_hash; ERROR: relation "drop_hash" does not exist 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 ab76d8596..980cf3a82 100644 --- a/src/test/regress/expected/isolation_ensure_dependency_activate_node.out +++ b/src/test/regress/expected/isolation_ensure_dependency_activate_node.out @@ -1235,7 +1235,7 @@ master_remove_node (2 rows) -starting permutation: s1-print-distributed-objects s1-add-worker s2-create-schema s2-begin s3-begin s3-use-schema s2-create-table s3-create-table s2-commit s3-commit s2-print-distributed-objects s3-drop-coordinator-schemas +starting permutation: s1-print-distributed-objects s2-create-table-for-colocation s1-add-worker s2-create-schema s2-begin s3-begin s3-use-schema s2-create-table s3-create-table s2-commit s3-commit s2-print-distributed-objects s3-drop-coordinator-schemas ?column? --------------------------------------------------------------------- 1 @@ -1306,6 +1306,16 @@ master_remove_node (1 row) +step s2-create-table-for-colocation: + CREATE SCHEMA col_schema; + CREATE TABLE col_schema.col_tbl (a INT, b INT); + SELECT create_distributed_table('col_schema.col_tbl', 'a'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + step s1-add-worker: SELECT 1 FROM master_add_node('localhost', 57638); @@ -1372,11 +1382,13 @@ pg_identify_object_as_address --------------------------------------------------------------------- (database,{regression},{}) (role,{postgres},{}) +(schema,{col_schema},{}) (schema,{myschema},{}) (schema,{public},{}) +(table,"{col_schema,col_tbl}",{}) (table,"{myschema,t1}",{}) (table,"{myschema,t2}",{}) -(6 rows) +(8 rows) count --------------------------------------------------------------------- @@ -2139,7 +2151,7 @@ master_remove_node (2 rows) -starting permutation: s1-print-distributed-objects s1-begin s1-add-worker s2-public-schema s2-distribute-function s1-commit s2-begin s2-commit s3-wait-for-metadata-sync s2-print-distributed-objects s3-drop-coordinator-schemas +starting permutation: s1-print-distributed-objects s2-create-table-for-colocation s1-begin s1-add-worker s2-public-schema s2-distribute-function s1-commit s2-begin s2-commit s3-wait-for-metadata-sync s2-print-distributed-objects s3-drop-coordinator-schemas ?column? --------------------------------------------------------------------- 1 @@ -2210,6 +2222,16 @@ master_remove_node (1 row) +step s2-create-table-for-colocation: + CREATE SCHEMA col_schema; + CREATE TABLE col_schema.col_tbl (a INT, b INT); + SELECT create_distributed_table('col_schema.col_tbl', 'a'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + step s1-begin: BEGIN; @@ -2269,8 +2291,10 @@ pg_identify_object_as_address (database,{regression},{}) (function,"{public,add}","{integer,integer}") (role,{postgres},{}) +(schema,{col_schema},{}) (schema,{public},{}) -(4 rows) +(table,"{col_schema,col_tbl}",{}) +(6 rows) count --------------------------------------------------------------------- @@ -2322,7 +2346,7 @@ master_remove_node (2 rows) -starting permutation: s1-print-distributed-objects s1-begin s2-public-schema s2-distribute-function s2-begin s2-commit s3-wait-for-metadata-sync s1-add-worker s1-commit s3-wait-for-metadata-sync s2-print-distributed-objects s3-drop-coordinator-schemas +starting permutation: s1-print-distributed-objects s2-create-table-for-colocation s1-begin s2-public-schema s2-distribute-function s2-begin s2-commit s3-wait-for-metadata-sync s1-add-worker s1-commit s3-wait-for-metadata-sync s2-print-distributed-objects s3-drop-coordinator-schemas ?column? --------------------------------------------------------------------- 1 @@ -2393,6 +2417,16 @@ master_remove_node (1 row) +step s2-create-table-for-colocation: + CREATE SCHEMA col_schema; + CREATE TABLE col_schema.col_tbl (a INT, b INT); + SELECT create_distributed_table('col_schema.col_tbl', 'a'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + step s1-begin: BEGIN; @@ -2459,8 +2493,10 @@ pg_identify_object_as_address (database,{regression},{}) (function,"{public,add}","{integer,integer}") (role,{postgres},{}) +(schema,{col_schema},{}) (schema,{public},{}) -(4 rows) +(table,"{col_schema,col_tbl}",{}) +(6 rows) count --------------------------------------------------------------------- @@ -2512,7 +2548,7 @@ master_remove_node (2 rows) -starting permutation: s1-print-distributed-objects s2-begin s2-create-schema s2-distribute-function s2-commit s3-wait-for-metadata-sync s1-begin s1-add-worker s1-commit s3-wait-for-metadata-sync s2-print-distributed-objects s3-drop-coordinator-schemas +starting permutation: s1-print-distributed-objects s2-create-table-for-colocation s2-begin s2-create-schema s2-distribute-function s2-commit s3-wait-for-metadata-sync s1-begin s1-add-worker s1-commit s3-wait-for-metadata-sync s2-print-distributed-objects s3-drop-coordinator-schemas ?column? --------------------------------------------------------------------- 1 @@ -2583,6 +2619,16 @@ master_remove_node (1 row) +step s2-create-table-for-colocation: + CREATE SCHEMA col_schema; + CREATE TABLE col_schema.col_tbl (a INT, b INT); + SELECT create_distributed_table('col_schema.col_tbl', 'a'); + +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + step s2-begin: BEGIN; @@ -2650,9 +2696,11 @@ pg_identify_object_as_address (database,{regression},{}) (function,"{myschema,add}","{integer,integer}") (role,{postgres},{}) +(schema,{col_schema},{}) (schema,{myschema},{}) (schema,{public},{}) -(5 rows) +(table,"{col_schema,col_tbl}",{}) +(7 rows) count --------------------------------------------------------------------- diff --git a/src/test/regress/expected/isolation_get_distributed_wait_queries_mx.out b/src/test/regress/expected/isolation_get_distributed_wait_queries_mx.out index 8cbedbf37..442974086 100644 --- a/src/test/regress/expected/isolation_get_distributed_wait_queries_mx.out +++ b/src/test/regress/expected/isolation_get_distributed_wait_queries_mx.out @@ -1200,7 +1200,7 @@ citus_remove_node (1 row) -starting permutation: s1-begin s1-update-ref-table-from-coordinator s2-start-session-level-connection s2-update-ref-table s3-select-distributed-waiting-queries s1-commit s2-stop-connection s5-begin s5-alter s6-select s3-select-distributed-waiting-queries s3-show-actual-gpids s5-rollback s8-begin s8-select s7-alter s3-select-distributed-waiting-queries s3-show-actual-gpids s8-rollback +starting permutation: s1-begin s1-update-ref-table-from-coordinator s2-start-session-level-connection s2-update-ref-table s3-select-distributed-waiting-queries s1-commit s2-stop-connection step s1-begin: BEGIN; @@ -1245,6 +1245,13 @@ stop_session_level_connection_to_node (1 row) +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s5-begin s5-alter s6-select s3-select-distributed-waiting-queries s5-rollback step s5-begin: BEGIN; @@ -1266,16 +1273,6 @@ blocked_statement |current_statement_in_b (1 row) -step s3-show-actual-gpids: - SELECT global_pid > 0 as gpid_exists, query FROM citus_stat_activity WHERE state = 'active' AND query IN (SELECT blocked_statement FROM citus_lock_waits UNION SELECT current_statement_in_blocking_process FROM citus_lock_waits) ORDER BY 1 DESC; - -gpid_exists|query ---------------------------------------------------------------------- -f | - SELECT user_id FROM tt1 ORDER BY user_id DESC LIMIT 1; - -(1 row) - step s5-rollback: ROLLBACK; @@ -1285,6 +1282,13 @@ user_id 7 (1 row) +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s8-begin s8-select s7-alter s3-select-distributed-waiting-queries s3-show-actual-gpids s8-rollback step s8-begin: BEGIN; diff --git a/src/test/regress/expected/isolation_logical_replication_binaryless.out b/src/test/regress/expected/isolation_logical_replication_binaryless.out new file mode 100644 index 000000000..383929e33 --- /dev/null +++ b/src/test/regress/expected/isolation_logical_replication_binaryless.out @@ -0,0 +1,48 @@ +Parsed test spec with 3 sessions + +starting permutation: s3-acquire-advisory-lock s1-move-placement s2-insert s3-release-advisory-lock s1-select +step s3-acquire-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s1-move-placement: + SELECT citus_move_shard_placement(45076800, 'localhost', 57637, 'localhost', 57638, shard_transfer_mode:='force_logical'); + +step s2-insert: + INSERT INTO t_nonbinary (SELECT i, 'user postgres=r/postgres' FROM generate_series(6, 10) i); + +step s3-release-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s1-move-placement: <... completed> +citus_move_shard_placement +--------------------------------------------------------------------- + +(1 row) + +step s1-select: + SELECT * FROM t_nonbinary order by id; + +id|nonbinary +--------------------------------------------------------------------- + 1|postgres=r/postgres + 2|postgres=r/postgres + 3|postgres=r/postgres + 4|postgres=r/postgres + 5|postgres=r/postgres + 6|postgres=r/postgres + 7|postgres=r/postgres + 8|postgres=r/postgres + 9|postgres=r/postgres +10|postgres=r/postgres +(10 rows) + diff --git a/src/test/regress/expected/isolation_ref2ref_foreign_keys.out b/src/test/regress/expected/isolation_ref2ref_foreign_keys.out index 4055a2f58..e3fec3d04 100644 --- a/src/test/regress/expected/isolation_ref2ref_foreign_keys.out +++ b/src/test/regress/expected/isolation_ref2ref_foreign_keys.out @@ -11,16 +11,27 @@ step s1-begin: BEGIN; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -ExclusiveLock| 1 -ShareLock | 1 + 0|8429800| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_1 SET id = 2 WHERE id = 1; + 0|8429800| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_1 SET id = 2 WHERE id = 1; (2 rows) step s1-rollback: @@ -30,13 +41,24 @@ step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -49,29 +71,51 @@ step s2-delete-table-1: DELETE FROM ref_table_1 WHERE id = 1; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -ExclusiveLock| 1 -ShareLock | 1 + 0|8429800| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_1 WHERE id = 1; + 0|8429800| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_1 WHERE id = 1; (2 rows) step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -84,29 +128,52 @@ step s2-update-table-2: UPDATE ref_table_2 SET id = 2 WHERE id = 1; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -ExclusiveLock| 2 -ShareLock | 1 -(2 rows) + 0|8429800| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_2 SET id = 2 WHERE id = 1; + 0|8429801| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_2 SET id = 2 WHERE id = 1; + 0|8429801| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_2 SET id = 2 WHERE id = 1; +(3 rows) step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -119,29 +186,52 @@ step s2-delete-table-2: DELETE FROM ref_table_2 WHERE id = 1; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -ExclusiveLock| 2 -ShareLock | 1 -(2 rows) + 0|8429800| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_2 WHERE id = 1; + 0|8429801| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_2 WHERE id = 1; + 0|8429801| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_2 WHERE id = 1; +(3 rows) step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -157,17 +247,30 @@ step s1-begin: BEGIN; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -ExclusiveLock| 3 -ShareLock | 1 -(2 rows) + 0|8429800| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_3 SET id = 2 WHERE id = 1; + 0|8429801| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_3 SET id = 2 WHERE id = 1; + 0|8429802| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_3 SET id = 2 WHERE id = 1; + 0|8429802| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| UPDATE ref_table_3 SET id = 2 WHERE id = 1; +(4 rows) step s1-rollback: ROLLBACK; @@ -176,13 +279,24 @@ step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -198,17 +312,30 @@ step s1-begin: BEGIN; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -ExclusiveLock| 3 -ShareLock | 1 -(2 rows) + 0|8429800| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_3 WHERE id = 1; + 0|8429801| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_3 WHERE id = 1; + 0|8429802| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_3 WHERE id = 1; + 0|8429802| 5|ExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| DELETE FROM ref_table_3 WHERE id = 1; +(4 rows) step s1-rollback: ROLLBACK; @@ -217,13 +344,24 @@ step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -236,29 +374,51 @@ step s2-insert-table-1: INSERT INTO ref_table_1 VALUES (7, 7); step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -RowExclusiveLock| 1 -ShareLock | 1 + 0|8429800| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_1 VALUES (7, 7); + 0|8429800| 5|RowExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_1 VALUES (7, 7); (2 rows) step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -271,29 +431,52 @@ step s2-insert-table-2: INSERT INTO ref_table_2 VALUES (7, 5); step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -RowExclusiveLock| 2 -ShareLock | 1 -(2 rows) + 0|8429800| 5|RowExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_2 VALUES (7, 5); + 0|8429801| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_2 VALUES (7, 5); + 0|8429801| 5|RowExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_2 VALUES (7, 5); +(3 rows) step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) @@ -306,29 +489,53 @@ step s2-insert-table-3: INSERT INTO ref_table_3 VALUES (7, 5); step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode |count +classid| objid|objsubid|mode |application_name |backend_type |query --------------------------------------------------------------------- -RowExclusiveLock| 3 -ShareLock | 1 -(2 rows) + 0|8429800| 5|RowExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_3 VALUES (7, 5); + 0|8429801| 5|RowExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_3 VALUES (7, 5); + 0|8429802| 4|ShareLock |isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_3 VALUES (7, 5); + 0|8429802| 5|RowExclusiveLock|isolation/isolation_ref2ref_foreign_keys/s2|client backend| INSERT INTO ref_table_3 VALUES (7, 5); +(4 rows) step s2-rollback: ROLLBACK; step s1-view-locks: - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; -mode|count +classid|objid|objsubid|mode|application_name|backend_type|query --------------------------------------------------------------------- (0 rows) diff --git a/src/test/regress/expected/isolation_ref_update_delete_upsert_vs_all_on_mx.out b/src/test/regress/expected/isolation_ref_update_delete_upsert_vs_all_on_mx.out index 3bdb73825..331745294 100644 --- a/src/test/regress/expected/isolation_ref_update_delete_upsert_vs_all_on_mx.out +++ b/src/test/regress/expected/isolation_ref_update_delete_upsert_vs_all_on_mx.out @@ -194,7 +194,7 @@ count (1 row) -starting permutation: s1-add-primary-key s1-start-session-level-connection s1-begin-on-worker s1-upsert s2-start-session-level-connection s2-begin-on-worker s2-drop s1-commit-worker s2-commit-worker s1-stop-connection s2-stop-connection s3-select-count +starting permutation: s1-add-primary-key s1-start-session-level-connection s1-begin-on-worker s1-upsert s2-drop s1-commit-worker s1-stop-connection s3-select-count create_reference_table --------------------------------------------------------------------- @@ -227,22 +227,6 @@ run_commands_on_session_level_connection_to_node (1 row) -step s2-start-session-level-connection: - SELECT start_session_level_connection_to_node('localhost', 57638); - -start_session_level_connection_to_node ---------------------------------------------------------------------- - -(1 row) - -step s2-begin-on-worker: - SELECT run_commands_on_session_level_connection_to_node('BEGIN'); - -run_commands_on_session_level_connection_to_node ---------------------------------------------------------------------- - -(1 row) - step s2-drop: DROP TABLE ref_table; @@ -255,14 +239,6 @@ run_commands_on_session_level_connection_to_node (1 row) step s2-drop: <... completed> -step s2-commit-worker: - SELECT run_commands_on_session_level_connection_to_node('COMMIT'); - -run_commands_on_session_level_connection_to_node ---------------------------------------------------------------------- - -(1 row) - step s1-stop-connection: SELECT stop_session_level_connection_to_node(); @@ -271,14 +247,6 @@ stop_session_level_connection_to_node (1 row) -step s2-stop-connection: - SELECT stop_session_level_connection_to_node(); - -stop_session_level_connection_to_node ---------------------------------------------------------------------- - -(1 row) - step s3-select-count: SELECT COUNT(*) FROM ref_table; diff --git a/src/test/regress/expected/isolation_tenant_isolation_nonblocking.out b/src/test/regress/expected/isolation_tenant_isolation_nonblocking.out index cd6b9fa22..7d8991615 100644 --- a/src/test/regress/expected/isolation_tenant_isolation_nonblocking.out +++ b/src/test/regress/expected/isolation_tenant_isolation_nonblocking.out @@ -8,9 +8,11 @@ create_distributed_table step s1-load-cache: TRUNCATE isolation_table; + TRUNCATE isolation_table2; step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -57,7 +59,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500076 + 1500078 (1 row) step s2-commit: @@ -76,9 +78,9 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500075|t | 0 - 57637|1500076|t | 1 57637|1500077|t | 0 + 57637|1500078|t | 1 + 57637|1500079|t | 0 57638|1500074|t | 0 (4 rows) @@ -96,9 +98,11 @@ create_distributed_table step s1-load-cache: TRUNCATE isolation_table; + TRUNCATE isolation_table2; step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -145,7 +149,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500082 + 1500086 (1 row) step s2-commit: @@ -164,10 +168,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500081|t | 0 - 57637|1500082|t | 0 - 57637|1500083|t | 0 - 57638|1500080|t | 0 + 57637|1500085|t | 0 + 57637|1500086|t | 0 + 57637|1500087|t | 0 + 57638|1500082|t | 0 (4 rows) id|value @@ -183,6 +187,7 @@ create_distributed_table step s1-load-cache: TRUNCATE isolation_table; + TRUNCATE isolation_table2; step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -214,6 +219,7 @@ step s2-isolate-tenant: step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s1-commit: COMMIT; @@ -229,7 +235,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500088 + 1500094 (1 row) step s2-commit: @@ -248,10 +254,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500087|t | 0 - 57637|1500088|t | 1 - 57637|1500089|t | 0 - 57638|1500086|t | 0 + 57637|1500093|t | 0 + 57637|1500094|t | 1 + 57637|1500095|t | 0 + 57638|1500090|t | 0 (4 rows) id|value @@ -268,6 +274,7 @@ create_distributed_table step s1-load-cache: TRUNCATE isolation_table; + TRUNCATE isolation_table2; step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -314,7 +321,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500094 + 1500102 (1 row) step s2-commit: @@ -333,10 +340,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500093|t | 1 - 57637|1500094|t | 1 - 57637|1500095|t | 2 - 57638|1500092|t | 1 + 57637|1500101|t | 1 + 57637|1500102|t | 1 + 57637|1500103|t | 2 + 57638|1500098|t | 1 (4 rows) id|value @@ -357,6 +364,7 @@ create_distributed_table step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -403,7 +411,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500100 + 1500110 (1 row) step s2-commit: @@ -422,10 +430,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500099|t | 0 - 57637|1500100|t | 1 - 57637|1500101|t | 0 - 57638|1500098|t | 0 + 57637|1500109|t | 0 + 57637|1500110|t | 1 + 57637|1500111|t | 0 + 57638|1500106|t | 0 (4 rows) id|value @@ -442,6 +450,7 @@ create_distributed_table step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -488,7 +497,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500106 + 1500118 (1 row) step s2-commit: @@ -507,10 +516,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500105|t | 0 - 57637|1500106|t | 0 - 57637|1500107|t | 0 - 57638|1500104|t | 0 + 57637|1500117|t | 0 + 57637|1500118|t | 0 + 57637|1500119|t | 0 + 57638|1500114|t | 0 (4 rows) id|value @@ -554,6 +563,7 @@ step s2-isolate-tenant: step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s1-commit: COMMIT; @@ -569,7 +579,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500112 + 1500126 (1 row) step s2-commit: @@ -588,10 +598,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500111|t | 0 - 57637|1500112|t | 1 - 57637|1500113|t | 0 - 57638|1500110|t | 0 + 57637|1500125|t | 0 + 57637|1500126|t | 1 + 57637|1500127|t | 0 + 57638|1500122|t | 0 (4 rows) id|value @@ -651,7 +661,7 @@ t step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500118 + 1500134 (1 row) step s2-commit: @@ -670,10 +680,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500117|t | 1 - 57637|1500118|t | 1 - 57637|1500119|t | 2 - 57638|1500116|t | 1 + 57637|1500133|t | 1 + 57637|1500134|t | 1 + 57637|1500135|t | 2 + 57638|1500130|t | 1 (4 rows) id|value @@ -686,7 +696,7 @@ id|value (5 rows) -starting permutation: s1-load-cache s1-insert s3-acquire-advisory-lock s1-begin s1-isolate-tenant s2-isolate-tenant s3-release-advisory-lock s1-commit s2-print-cluster +starting permutation: s1-load-cache s1-insert s3-acquire-advisory-lock s2-isolate-tenant s1-isolate-tenant-same-coloc s3-release-advisory-lock s2-print-cluster create_distributed_table --------------------------------------------------------------------- @@ -694,9 +704,11 @@ create_distributed_table step s1-load-cache: TRUNCATE isolation_table; + TRUNCATE isolation_table2; step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -706,17 +718,11 @@ pg_advisory_lock (1 row) -step s1-begin: - BEGIN; - -- the tests are written with the logic where single shard SELECTs - -- do not to open transaction blocks - SET citus.select_opens_transaction_block TO false; - -step s1-isolate-tenant: - SELECT isolate_tenant_to_new_shard('isolation_table', 2, shard_transfer_mode => 'force_logical'); - -step s2-isolate-tenant: +step s2-isolate-tenant: SELECT isolate_tenant_to_new_shard('isolation_table', 5, shard_transfer_mode => 'force_logical'); + +step s1-isolate-tenant-same-coloc: + SELECT isolate_tenant_to_new_shard('isolation_table', 2, shard_transfer_mode => 'force_logical'); ERROR: could not acquire the lock required to split public.isolation_table step s3-release-advisory-lock: @@ -727,15 +733,12 @@ pg_advisory_unlock t (1 row) -step s1-isolate-tenant: <... completed> +step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500124 + 1500142 (1 row) -step s1-commit: - COMMIT; - step s2-print-cluster: -- row count per shard SELECT @@ -749,10 +752,10 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500121|t | 1 - 57638|1500123|t | 0 - 57638|1500124|t | 0 - 57638|1500125|t | 0 + 57637|1500141|t | 0 + 57637|1500142|t | 1 + 57637|1500143|t | 0 + 57638|1500138|t | 0 (4 rows) id|value @@ -761,14 +764,19 @@ id|value (1 row) -starting permutation: s1-insert s3-acquire-advisory-lock s1-begin s1-isolate-tenant s2-isolate-tenant s3-release-advisory-lock s1-commit s2-print-cluster +starting permutation: s1-load-cache s1-insert s3-acquire-advisory-lock s2-isolate-tenant s1-isolate-tenant-same-coloc-blocking s3-release-advisory-lock s2-print-cluster create_distributed_table --------------------------------------------------------------------- (1 row) +step s1-load-cache: + TRUNCATE isolation_table; + TRUNCATE isolation_table2; + step s1-insert: INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); step s3-acquire-advisory-lock: SELECT pg_advisory_lock(44000, 55152); @@ -778,17 +786,11 @@ pg_advisory_lock (1 row) -step s1-begin: - BEGIN; - -- the tests are written with the logic where single shard SELECTs - -- do not to open transaction blocks - SET citus.select_opens_transaction_block TO false; - -step s1-isolate-tenant: - SELECT isolate_tenant_to_new_shard('isolation_table', 2, shard_transfer_mode => 'force_logical'); - -step s2-isolate-tenant: +step s2-isolate-tenant: SELECT isolate_tenant_to_new_shard('isolation_table', 5, shard_transfer_mode => 'force_logical'); + +step s1-isolate-tenant-same-coloc-blocking: + SELECT isolate_tenant_to_new_shard('isolation_table', 2, shard_transfer_mode => 'block_writes'); ERROR: could not acquire the lock required to split public.isolation_table step s3-release-advisory-lock: @@ -799,13 +801,152 @@ pg_advisory_unlock t (1 row) -step s1-isolate-tenant: <... completed> +step s2-isolate-tenant: <... completed> isolate_tenant_to_new_shard --------------------------------------------------------------------- - 1500130 + 1500150 (1 row) -step s1-commit: +step s2-print-cluster: + -- row count per shard + SELECT + nodeport, shardid, success, result + FROM + run_command_on_placements('isolation_table', 'select count(*) from %s') + ORDER BY + nodeport, shardid; + -- rows + SELECT id, value FROM isolation_table ORDER BY id, value; + +nodeport|shardid|success|result +--------------------------------------------------------------------- + 57637|1500149|t | 0 + 57637|1500150|t | 1 + 57637|1500151|t | 0 + 57638|1500146|t | 0 +(4 rows) + +id|value +--------------------------------------------------------------------- + 5| 10 +(1 row) + + +starting permutation: s1-load-cache s1-insert s3-acquire-advisory-lock s2-isolate-tenant s1-isolate-tenant-no-same-coloc s3-release-advisory-lock s2-print-cluster +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s1-load-cache: + TRUNCATE isolation_table; + TRUNCATE isolation_table2; + +step s1-insert: + INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); + +step s3-acquire-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s2-isolate-tenant: + SELECT isolate_tenant_to_new_shard('isolation_table', 5, shard_transfer_mode => 'force_logical'); + +step s1-isolate-tenant-no-same-coloc: + SELECT isolate_tenant_to_new_shard('isolation_table2', 2, shard_transfer_mode => 'force_logical'); + +ERROR: could not acquire the lock required to split concurrently public.isolation_table2. +step s3-release-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s2-isolate-tenant: <... completed> +isolate_tenant_to_new_shard +--------------------------------------------------------------------- + 1500158 +(1 row) + +step s2-print-cluster: + -- row count per shard + SELECT + nodeport, shardid, success, result + FROM + run_command_on_placements('isolation_table', 'select count(*) from %s') + ORDER BY + nodeport, shardid; + -- rows + SELECT id, value FROM isolation_table ORDER BY id, value; + +nodeport|shardid|success|result +--------------------------------------------------------------------- + 57637|1500157|t | 0 + 57637|1500158|t | 1 + 57637|1500159|t | 0 + 57638|1500154|t | 0 +(4 rows) + +id|value +--------------------------------------------------------------------- + 5| 10 +(1 row) + + +starting permutation: s1-load-cache s1-insert s3-acquire-advisory-lock s2-begin s2-isolate-tenant s1-isolate-tenant-no-same-coloc s3-release-advisory-lock s2-commit s2-print-cluster +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s1-load-cache: + TRUNCATE isolation_table; + TRUNCATE isolation_table2; + +step s1-insert: + INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); + +step s3-acquire-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s2-begin: + BEGIN; + +step s2-isolate-tenant: + SELECT isolate_tenant_to_new_shard('isolation_table', 5, shard_transfer_mode => 'force_logical'); + +step s1-isolate-tenant-no-same-coloc: + SELECT isolate_tenant_to_new_shard('isolation_table2', 2, shard_transfer_mode => 'force_logical'); + +ERROR: could not acquire the lock required to split concurrently public.isolation_table2. +step s3-release-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s2-isolate-tenant: <... completed> +isolate_tenant_to_new_shard +--------------------------------------------------------------------- + 1500169 +(1 row) + +step s2-commit: COMMIT; step s2-print-cluster: @@ -821,10 +962,154 @@ step s2-print-cluster: nodeport|shardid|success|result --------------------------------------------------------------------- - 57637|1500127|t | 1 - 57638|1500129|t | 0 - 57638|1500130|t | 0 - 57638|1500131|t | 0 + 57637|1500168|t | 0 + 57637|1500169|t | 1 + 57637|1500170|t | 0 + 57638|1500165|t | 0 +(4 rows) + +id|value +--------------------------------------------------------------------- + 5| 10 +(1 row) + + +starting permutation: s1-load-cache s1-insert s3-acquire-advisory-lock s2-isolate-tenant s1-isolate-tenant-no-same-coloc-blocking s3-release-advisory-lock s2-print-cluster +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s1-load-cache: + TRUNCATE isolation_table; + TRUNCATE isolation_table2; + +step s1-insert: + INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); + +step s3-acquire-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s2-isolate-tenant: + SELECT isolate_tenant_to_new_shard('isolation_table', 5, shard_transfer_mode => 'force_logical'); + +step s1-isolate-tenant-no-same-coloc-blocking: + SELECT isolate_tenant_to_new_shard('isolation_table2', 2, shard_transfer_mode => 'block_writes'); + +isolate_tenant_to_new_shard +--------------------------------------------------------------------- + 1500183 +(1 row) + +step s3-release-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s2-isolate-tenant: <... completed> +isolate_tenant_to_new_shard +--------------------------------------------------------------------- + 1500180 +(1 row) + +step s2-print-cluster: + -- row count per shard + SELECT + nodeport, shardid, success, result + FROM + run_command_on_placements('isolation_table', 'select count(*) from %s') + ORDER BY + nodeport, shardid; + -- rows + SELECT id, value FROM isolation_table ORDER BY id, value; + +nodeport|shardid|success|result +--------------------------------------------------------------------- + 57637|1500179|t | 0 + 57637|1500180|t | 1 + 57637|1500181|t | 0 + 57638|1500176|t | 0 +(4 rows) + +id|value +--------------------------------------------------------------------- + 5| 10 +(1 row) + + +starting permutation: s1-load-cache s1-insert s3-acquire-advisory-lock s2-isolate-tenant s1-isolate-tenant-no-same-coloc-blocking s3-release-advisory-lock s2-print-cluster +create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +step s1-load-cache: + TRUNCATE isolation_table; + TRUNCATE isolation_table2; + +step s1-insert: + INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); + +step s3-acquire-advisory-lock: + SELECT pg_advisory_lock(44000, 55152); + +pg_advisory_lock +--------------------------------------------------------------------- + +(1 row) + +step s2-isolate-tenant: + SELECT isolate_tenant_to_new_shard('isolation_table', 5, shard_transfer_mode => 'force_logical'); + +step s1-isolate-tenant-no-same-coloc-blocking: + SELECT isolate_tenant_to_new_shard('isolation_table2', 2, shard_transfer_mode => 'block_writes'); + +isolate_tenant_to_new_shard +--------------------------------------------------------------------- + 1500194 +(1 row) + +step s3-release-advisory-lock: + SELECT pg_advisory_unlock(44000, 55152); + +pg_advisory_unlock +--------------------------------------------------------------------- +t +(1 row) + +step s2-isolate-tenant: <... completed> +isolate_tenant_to_new_shard +--------------------------------------------------------------------- + 1500191 +(1 row) + +step s2-print-cluster: + -- row count per shard + SELECT + nodeport, shardid, success, result + FROM + run_command_on_placements('isolation_table', 'select count(*) from %s') + ORDER BY + nodeport, shardid; + -- rows + SELECT id, value FROM isolation_table ORDER BY id, value; + +nodeport|shardid|success|result +--------------------------------------------------------------------- + 57637|1500190|t | 0 + 57637|1500191|t | 1 + 57637|1500192|t | 0 + 57638|1500187|t | 0 (4 rows) id|value diff --git a/src/test/regress/expected/issue_5248.out b/src/test/regress/expected/issue_5248.out index 7f9e45417..4639f24ed 100644 --- a/src/test/regress/expected/issue_5248.out +++ b/src/test/regress/expected/issue_5248.out @@ -1,8 +1,19 @@ +-- +-- ISSUE_5248 +-- +-- This test file has an alternative output because of the change in the +-- backup modes of Postgres. Specifically, there is a renaming +-- issue: pg_stop_backup PRE PG15 vs pg_backup_stop PG15+ +-- The alternative output can be deleted when we drop support for PG14 +-- CREATE SCHEMA issue_5248; SET search_path TO issue_5248; SET citus.shard_count TO 4; SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 3013000; +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset create table countries( id serial primary key , name text @@ -209,8 +220,12 @@ FROM ( ( SELECT utc_offset FROM pg_catalog.pg_timezone_names limit 1 offset 4) limit 91) AS subq_3 -WHERE pg_catalog.pg_stop_backup() > cast(NULL AS pg_lsn) limit 100; +\if :server_version_ge_15 +WHERE pg_catalog.pg_backup_stop() > cast(NULL AS record) limit 100; 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 +\else +WHERE pg_catalog.pg_stop_backup() > cast(NULL AS pg_lsn) limit 100; +\endif SET client_min_messages TO WARNING; DROP SCHEMA issue_5248 CASCADE; diff --git a/src/test/regress/expected/issue_5248_0.out b/src/test/regress/expected/issue_5248_0.out new file mode 100644 index 000000000..cb3c4562f --- /dev/null +++ b/src/test/regress/expected/issue_5248_0.out @@ -0,0 +1,231 @@ +-- +-- ISSUE_5248 +-- +-- This test file has an alternative output because of the change in the +-- backup modes of Postgres. Specifically, there is a renaming +-- issue: pg_stop_backup PRE PG15 vs pg_backup_stop PG15+ +-- The alternative output can be deleted when we drop support for PG14 +-- +CREATE SCHEMA issue_5248; +SET search_path TO issue_5248; +SET citus.shard_count TO 4; +SET citus.shard_replication_factor TO 1; +SET citus.next_shard_id TO 3013000; +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +create table countries( + id serial primary key + , name text + , code varchar(2) collate "C" unique +); +insert into countries(name, code) select 'country-'||i, i::text from generate_series(10,99) i; +select create_reference_table('countries'); +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($$issue_5248.countries$$) + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +create table orgs ( + id bigserial primary key + , name text + , created_at timestamptz default now() +); +select create_distributed_table('orgs', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +create table users ( + id bigserial + , org_id bigint references orgs(id) + , name text + , created_at timestamptz default now() + , country_id int -- references countries(id) + , score bigint generated always as (id + country_id) stored + , primary key (org_id, id) +); +select create_distributed_table('users', 'org_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +alter table users add constraint fk_user_country foreign key (country_id) references countries(id); +create table orders ( + id bigserial + , org_id bigint references orgs(id) + , user_id bigint + , price int + , info jsonb + , primary key (org_id, id) + , foreign key (org_id, user_id) references users(org_id, id) +); +select create_distributed_table('orders', 'org_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +create table events ( + id bigserial not null + , user_id bigint not null + , org_id bigint not null + , event_time timestamp not null default now() + , event_type int not null default 0 + , payload jsonb + , primary key (user_id, id) +); +create index event_time_idx on events using BRIN (event_time); +create index event_json_idx on events using gin(payload); +select create_distributed_table('events', 'user_id'); -- on purpose don't colocate on correctly on org_id + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +create table local_data( + id bigserial primary key + , val int default ( (random()*100)::int ) +); +insert into orgs(id, name) select i,'org-'||i from generate_series(1,10) i; +insert into users(id, name, org_id, country_id) select i,'user-'||i, i+1, (i%90)+1 from generate_series(1,5) i; +insert into orders(id, org_id, user_id, price) select i, ((i+1))+1 , i+1, i/100 from generate_series(1,2) i; +insert into events(id, org_id, user_id, event_type) select i, ((i+1))+1 , i+1, i/100 from generate_series(1,10) i; +insert into local_data(id) select generate_series(1,10); +/* + * Test that we don't get a crash. See #5248. + */ +SELECT subq_3.c15 AS c0, + subq_3.c0 AS c1, + subq_3.c15 AS c2, + subq_0.c1 AS c3, + pg_catalog.String_agg( Cast( + ( + SELECT tgargs + FROM pg_catalog.pg_trigger limit 1 offset 1) AS BYTEA), Cast( + ( + SELECT minimum_value + FROM columnar.chunk limit 1 offset 5) AS BYTEA)) OVER (partition BY subq_3.c10 ORDER BY subq_3.c12,subq_0.c2) AS c4, + subq_0.c1 AS c5 +FROM ( + SELECT ref_1.address AS c0, + ref_1.error AS c1, + sample_0.NAME AS c2, + sample_2.trftosql AS c3 + FROM pg_catalog.pg_statio_all_sequences AS ref_0 + INNER JOIN pg_catalog.pg_hba_file_rules AS ref_1 + ON (( + SELECT pg_catalog.Max(aggnumdirectargs) + FROM pg_catalog.pg_aggregate) <= ref_0.blks_hit) + INNER JOIN countries AS sample_0 TABLESAMPLE system (6.4) + INNER JOIN local_data AS sample_1 TABLESAMPLE bernoulli (8) + ON (( + true) + OR ( + sample_0.NAME IS NOT NULL)) + INNER JOIN pg_catalog.pg_transform AS sample_2 TABLESAMPLE bernoulli (1.2) + INNER JOIN pg_catalog.pg_language AS ref_2 + ON (( + SELECT shard_cost_function + FROM pg_catalog.pg_dist_rebalance_strategy limit 1 offset 1) IS NULL) + RIGHT JOIN pg_catalog.pg_index AS sample_3 TABLESAMPLE system (0.3) + ON (( + cast(NULL AS bpchar) ~<=~ cast(NULL AS bpchar)) + OR (( + EXISTS + ( + SELECT sample_3.indnkeyatts AS c0, + sample_2.trflang AS c1, + sample_2.trftype AS c2 + FROM pg_catalog.pg_statistic_ext AS sample_4 TABLESAMPLE bernoulli (8.6) + WHERE sample_2.trftype IS NOT NULL)) + AND ( + false))) + ON ( + EXISTS + ( + SELECT sample_0.id AS c0, + sample_3.indisprimary AS c1 + FROM orgs AS sample_5 TABLESAMPLE system (5.3) + WHERE false)) + ON ( + cast(NULL AS float8) > + ( + SELECT pg_catalog.avg(enumsortorder) + FROM pg_catalog.pg_enum) ) + WHERE cast(COALESCE( + CASE + WHEN ref_1.auth_method ~>=~ ref_1.auth_method THEN cast(NULL AS path) + ELSE cast(NULL AS path) + END , cast(NULL AS path)) AS path) = cast(NULL AS path)) AS subq_0, + lateral + ( + SELECT + ( + SELECT pg_catalog.stddev(total_time) + FROM pg_catalog.pg_stat_user_functions) AS c0, + subq_0.c1 AS c1, + subq_2.c0 AS c2, + subq_0.c2 AS c3, + subq_0.c0 AS c4, + cast(COALESCE(subq_2.c0, subq_2.c0) AS text) AS c5, + subq_2.c0 AS c6, + subq_2.c1 AS c7, + subq_2.c1 AS c8, + subq_2.c1 AS c9, + subq_0.c3 AS c10, + pg_catalog.pg_stat_get_db_temp_files( cast( + ( + SELECT objoid + FROM pg_catalog.pg_description limit 1 offset 1) AS oid)) AS c11, + subq_0.c3 AS c12, + subq_2.c1 AS c13, + subq_0.c0 AS c14, + subq_0.c3 AS c15, + subq_0.c3 AS c16, + subq_0.c1 AS c17, + subq_0.c2 AS c18 + FROM ( + SELECT subq_1.c2 AS c0, + subq_0.c3 AS c1 + FROM information_schema.element_types AS ref_3, + lateral + ( + SELECT subq_0.c1 AS c0, + sample_6.info AS c1, + subq_0.c2 AS c2, + subq_0.c3 AS c3, + ref_3.domain_default AS c4, + sample_6.user_id AS c5, + ref_3.collation_name AS c6 + FROM orders AS sample_6 TABLESAMPLE system (3.8) + WHERE sample_6.price = sample_6.org_id limit 58) AS subq_1 + WHERE ( + subq_1.c2 <= subq_0.c2) + AND ( + cast(NULL AS line) ?-| cast(NULL AS line)) limit 59) AS subq_2 + WHERE cast(COALESCE(pg_catalog.age( cast( + ( + SELECT pg_catalog.max(event_time) + FROM events) AS "timestamp")), + ( + SELECT write_lag + FROM pg_catalog.pg_stat_replication limit 1 offset 3) ) AS "interval") > + ( + SELECT utc_offset + FROM pg_catalog.pg_timezone_names limit 1 offset 4) limit 91) AS subq_3 +\if :server_version_ge_15 +WHERE pg_catalog.pg_backup_stop() > cast(NULL AS record) limit 100; +\else +WHERE pg_catalog.pg_stop_backup() > cast(NULL AS pg_lsn) limit 100; +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 +\endif +SET client_min_messages TO WARNING; +DROP SCHEMA issue_5248 CASCADE; diff --git a/src/test/regress/expected/json_table_select_only.out b/src/test/regress/expected/json_table_select_only.out new file mode 100644 index 000000000..0ce4edc68 --- /dev/null +++ b/src/test/regress/expected/json_table_select_only.out @@ -0,0 +1,1583 @@ +-- +-- PG15+ test +-- +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 +SET search_path TO "json table"; +CREATE SCHEMA "json table"; +SET search_path TO "json table"; +CREATE TABLE jsonb_table_test (id bigserial, js jsonb); +SELECT create_distributed_table('jsonb_table_test', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- insert some data +INSERT INTO jsonb_table_test (js) +VALUES ( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "d": [], "c": []}, + {"a": 2, "d": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "d": [1, 2], "c": []}, + {"x": "4", "d": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [100, 200, 300], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": [null]}, + {"x": "4", "b": [1, 2], "c": 2} + ]' +), +( + '[ + {"y": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "t": [1, 2], "c": []}, + {"x": "4", "b": [1, 200], "c": 96} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "100", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"t": 1, "b": [], "c": []}, + {"t": 2, "b": [1, 2, 3], "x": [10, null, 20]}, + {"t": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"U": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1000, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "T": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"ffa": 1, "b": [], "c": []}, + {"ffb": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"fffc": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt ORDER BY 1,2,3,4; + n | a | c | b +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 10 | + 2 | -1 | 20 | + 2 | -1 | | 1 + 2 | -1 | | 1 + 2 | -1 | | 2 + 2 | -1 | | 2 + 2 | -1 | | 3 + 2 | -1 | | 3 + 2 | -1 | | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 100 + 2 | 2 | | 200 + 2 | 2 | | 300 + 2 | 2 | | 1000 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | | 1 + 3 | -1 | | 1 + 3 | -1 | | 2 + 3 | -1 | | 2 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 200 + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (inner, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (inner) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- default plan (inner, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- default plan (outer, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 rows) + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 rows) + +select + jt.*, b1 + 100 as b +from + json_table (jsonb + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt ORDER BY 1,2,3,4,5; + n | a | b | b1 | c | c1 | b +--------------------------------------------------------------------- + 1 | 1 | [1, 10] | 1 | 1 | | 101 + 1 | 1 | [1, 10] | 1 | 2 | | 101 + 1 | 1 | [1, 10] | 1 | null | | 101 + 1 | 1 | [1, 10] | 10 | 1 | | 110 + 1 | 1 | [1, 10] | 10 | 2 | | 110 + 1 | 1 | [1, 10] | 10 | null | | 110 + 1 | 1 | [2] | 2 | 1 | | 102 + 1 | 1 | [2] | 2 | 2 | | 102 + 1 | 1 | [2] | 2 | null | | 102 + 1 | 1 | [3, 30, 300] | 3 | 1 | | 103 + 1 | 1 | [3, 30, 300] | 3 | 2 | | 103 + 1 | 1 | [3, 30, 300] | 3 | null | | 103 + 1 | 1 | [3, 30, 300] | 30 | 1 | | 130 + 1 | 1 | [3, 30, 300] | 30 | 2 | | 130 + 1 | 1 | [3, 30, 300] | 30 | null | | 130 + 1 | 1 | [3, 30, 300] | 300 | 1 | | 400 + 1 | 1 | [3, 30, 300] | 300 | 2 | | 400 + 1 | 1 | [3, 30, 300] | 300 | null | | 400 + 2 | 2 | | | | | + 3 | | | | | | +(20 rows) + +-- Should succeed (JSON arguments are passed to root and nested paths) +SELECT * +FROM + generate_series(1, 4) x, + generate_series(1, 3) y, + JSON_TABLE(jsonb + '[[1,2,3],[2,3,4,5],[3,4,5,6]]', + 'strict $[*] ? (@[*] < $x)' + PASSING x AS x, y AS y + COLUMNS ( + y text FORMAT JSON PATH '$', + NESTED PATH 'strict $[*] ? (@ >= $y)' + COLUMNS ( + z int PATH '$' + ) + ) + ) jt ORDER BY 4,1,2,3; + x | y | y | z +--------------------------------------------------------------------- + 2 | 1 | [1, 2, 3] | 1 + 3 | 1 | [1, 2, 3] | 1 + 4 | 1 | [1, 2, 3] | 1 + 2 | 1 | [1, 2, 3] | 2 + 2 | 2 | [1, 2, 3] | 2 + 3 | 1 | [1, 2, 3] | 2 + 3 | 1 | [2, 3, 4, 5] | 2 + 3 | 2 | [1, 2, 3] | 2 + 3 | 2 | [2, 3, 4, 5] | 2 + 4 | 1 | [1, 2, 3] | 2 + 4 | 1 | [2, 3, 4, 5] | 2 + 4 | 2 | [1, 2, 3] | 2 + 4 | 2 | [2, 3, 4, 5] | 2 + 2 | 1 | [1, 2, 3] | 3 + 2 | 2 | [1, 2, 3] | 3 + 2 | 3 | [1, 2, 3] | 3 + 3 | 1 | [1, 2, 3] | 3 + 3 | 1 | [2, 3, 4, 5] | 3 + 3 | 2 | [1, 2, 3] | 3 + 3 | 2 | [2, 3, 4, 5] | 3 + 3 | 3 | [1, 2, 3] | 3 + 3 | 3 | [2, 3, 4, 5] | 3 + 4 | 1 | [1, 2, 3] | 3 + 4 | 1 | [2, 3, 4, 5] | 3 + 4 | 1 | [3, 4, 5, 6] | 3 + 4 | 2 | [1, 2, 3] | 3 + 4 | 2 | [2, 3, 4, 5] | 3 + 4 | 2 | [3, 4, 5, 6] | 3 + 4 | 3 | [1, 2, 3] | 3 + 4 | 3 | [2, 3, 4, 5] | 3 + 4 | 3 | [3, 4, 5, 6] | 3 + 3 | 1 | [2, 3, 4, 5] | 4 + 3 | 2 | [2, 3, 4, 5] | 4 + 3 | 3 | [2, 3, 4, 5] | 4 + 4 | 1 | [2, 3, 4, 5] | 4 + 4 | 1 | [3, 4, 5, 6] | 4 + 4 | 2 | [2, 3, 4, 5] | 4 + 4 | 2 | [3, 4, 5, 6] | 4 + 4 | 3 | [2, 3, 4, 5] | 4 + 4 | 3 | [3, 4, 5, 6] | 4 + 3 | 1 | [2, 3, 4, 5] | 5 + 3 | 2 | [2, 3, 4, 5] | 5 + 3 | 3 | [2, 3, 4, 5] | 5 + 4 | 1 | [2, 3, 4, 5] | 5 + 4 | 1 | [3, 4, 5, 6] | 5 + 4 | 2 | [2, 3, 4, 5] | 5 + 4 | 2 | [3, 4, 5, 6] | 5 + 4 | 3 | [2, 3, 4, 5] | 5 + 4 | 3 | [3, 4, 5, 6] | 5 + 4 | 1 | [3, 4, 5, 6] | 6 + 4 | 2 | [3, 4, 5, 6] | 6 + 4 | 3 | [3, 4, 5, 6] | 6 +(52 rows) + +SET client_min_messages TO ERROR; +DROP SCHEMA "json table" CASCADE; diff --git a/src/test/regress/expected/json_table_select_only_0.out b/src/test/regress/expected/json_table_select_only_0.out new file mode 100644 index 000000000..c04e76814 --- /dev/null +++ b/src/test/regress/expected/json_table_select_only_0.out @@ -0,0 +1,9 @@ +-- +-- PG15+ test +-- +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/local_shard_execution.out b/src/test/regress/expected/local_shard_execution.out index 1f7e3e80c..59f59d4b3 100644 --- a/src/test/regress/expected/local_shard_execution.out +++ b/src/test/regress/expected/local_shard_execution.out @@ -1,3 +1,17 @@ +-- +-- LOCAL_SHARD_EXECUTION +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA local_shard_execution; SET search_path TO local_shard_execution; SET citus.shard_count TO 4; @@ -288,7 +302,7 @@ RETURNING *; INSERT INTO distributed_table SELECT * FROM distributed_table WHERE key = 1 OFFSET 0 ON CONFLICT DO NOTHING; NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) OFFSET 0 NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) SELECT key, value, age FROM read_intermediate_result('insert_select_XXX_1470001'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint) ON CONFLICT DO NOTHING +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('insert_select_XXX_1470001'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint) ON CONFLICT DO NOTHING INSERT INTO distributed_table SELECT 1, '1',15 FROM distributed_table WHERE key = 2 LIMIT 1 ON CONFLICT DO NOTHING; -- sanity check: multi-shard INSERT..SELECT pushdown goes through distributed execution INSERT INTO distributed_table SELECT * FROM distributed_table ON CONFLICT DO NOTHING; @@ -800,7 +814,7 @@ NOTICE: executing the copy locally for shard xxxxx INSERT INTO distributed_table (key) SELECT -key FROM distributed_table; NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1470001_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1470001_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1470003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1470003_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 -NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key) SELECT key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1470003_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key) SELECT intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1470003_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) SELECT count(*) FROM distributed_table WHERE key = -6; NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) '-6'::integer) count diff --git a/src/test/regress/expected/local_shard_execution_0.out b/src/test/regress/expected/local_shard_execution_0.out new file mode 100644 index 000000000..2d0d7f089 --- /dev/null +++ b/src/test/regress/expected/local_shard_execution_0.out @@ -0,0 +1,3297 @@ +-- +-- LOCAL_SHARD_EXECUTION +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA local_shard_execution; +SET search_path TO local_shard_execution; +SET citus.shard_count TO 4; +SET citus.shard_replication_factor TO 1; +SET citus.next_shard_id TO 1470000; +CREATE TABLE reference_table (key int PRIMARY KEY); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE distributed_table (key int PRIMARY KEY , value text, age bigint CHECK (age > 10), FOREIGN KEY (key) REFERENCES reference_table(key) ON DELETE CASCADE); +SELECT create_distributed_table('distributed_table','key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE second_distributed_table (key int PRIMARY KEY , value text, FOREIGN KEY (key) REFERENCES distributed_table(key) ON DELETE CASCADE); +SELECT create_distributed_table('second_distributed_table','key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- ingest some data to enable some tests with data +INSERT INTO reference_table VALUES (1); +INSERT INTO distributed_table VALUES (1, '1', 20); +INSERT INTO second_distributed_table VALUES (1, '1'); +-- a simple test for +CREATE TABLE collections_list ( + key bigserial, + ser bigserial, + ts timestamptz, + collection_id integer, + value numeric, + PRIMARY KEY(key, collection_id) +) PARTITION BY LIST (collection_id ); +SELECT create_distributed_table('collections_list', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE collections_list_0 + PARTITION OF collections_list (key, ser, ts, collection_id, value) + FOR VALUES IN ( 0 ); +-- create a volatile function that returns the local node id +CREATE OR REPLACE FUNCTION get_local_node_id_volatile() +RETURNS INT AS $$ +DECLARE localGroupId int; +BEGIN + SELECT groupid INTO localGroupId FROM pg_dist_local_group; + RETURN localGroupId; +END; $$ language plpgsql VOLATILE; +SELECT create_distributed_function('get_local_node_id_volatile()'); +NOTICE: procedure local_shard_execution.get_local_node_id_volatile is already distributed +DETAIL: Citus distributes procedures with CREATE [PROCEDURE|FUNCTION|AGGREGATE] commands + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +-- test case for issue #3556 +CREATE TABLE accounts (id text PRIMARY KEY); +CREATE TABLE stats (account_id text PRIMARY KEY, spent int); +SELECT create_distributed_table('accounts', 'id', colocate_with => 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('stats', 'account_id', colocate_with => 'accounts'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO accounts (id) VALUES ('foo'); +INSERT INTO stats (account_id, spent) VALUES ('foo', 100); +CREATE TABLE abcd(a int, b int, c int, d int); +SELECT create_distributed_table('abcd', 'b'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO abcd VALUES (1,2,3,4); +INSERT INTO abcd VALUES (2,3,4,5); +INSERT INTO abcd VALUES (3,4,5,6); +ALTER TABLE abcd DROP COLUMN a; +-- connection worker and get ready for the tests +\c - - - :worker_1_port +SET search_path TO local_shard_execution; +SET citus.enable_unique_job_ids TO off; +-- returns true of the distribution key filter +-- on the distributed tables (e.g., WHERE key = 1), we'll hit a shard +-- placement which is local to this not +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE FUNCTION shard_of_distribution_column_is_local(dist_key int) RETURNS bool AS $$ + + DECLARE shard_is_local BOOLEAN := FALSE; + + BEGIN + + WITH local_shard_ids AS (SELECT get_shard_id_for_distribution_column('local_shard_execution.distributed_table', dist_key)), + all_local_shard_ids_on_node AS (SELECT shardid FROM pg_dist_placement WHERE groupid IN (SELECT groupid FROM pg_dist_local_group)) + SELECT + true INTO shard_is_local + FROM + local_shard_ids + WHERE + get_shard_id_for_distribution_column IN (SELECT * FROM all_local_shard_ids_on_node); + + IF shard_is_local IS NULL THEN + shard_is_local = FALSE; + END IF; + + RETURN shard_is_local; + END; +$$ LANGUAGE plpgsql; +RESET citus.enable_metadata_sync; +-- test case for issue #3556 +SET citus.log_intermediate_results TO TRUE; +SET client_min_messages TO DEBUG1; +SELECT * +FROM +( + WITH accounts_cte AS ( + SELECT id AS account_id + FROM accounts + ), + joined_stats_cte_1 AS ( + SELECT spent, account_id + FROM stats + INNER JOIN accounts_cte USING (account_id) + ), + joined_stats_cte_2 AS ( + SELECT spent, account_id + FROM joined_stats_cte_1 + INNER JOIN accounts_cte USING (account_id) + ) + SELECT SUM(spent) OVER (PARTITION BY coalesce(account_id, NULL)) + FROM accounts_cte + INNER JOIN joined_stats_cte_2 USING (account_id) +) inner_query; +DEBUG: CTE joined_stats_cte_1 is going to be inlined via distributed planning +DEBUG: CTE joined_stats_cte_2 is going to be inlined via distributed planning +DEBUG: generating subplan XXX_1 for CTE accounts_cte: SELECT id AS account_id FROM local_shard_execution.accounts +DEBUG: generating subplan XXX_2 for subquery SELECT sum(joined_stats_cte_2.spent) OVER (PARTITION BY COALESCE(accounts_cte.account_id, NULL::text)) AS sum FROM ((SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte JOIN (SELECT joined_stats_cte_1.spent, joined_stats_cte_1.account_id FROM ((SELECT stats.spent, stats.account_id FROM (local_shard_execution.stats JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte_2 USING (account_id))) joined_stats_cte_1 JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte_1 USING (account_id))) joined_stats_cte_2 USING (account_id)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum FROM (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint)) inner_query +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file + sum +--------------------------------------------------------------------- + 100 +(1 row) + +SET citus.log_intermediate_results TO DEFAULT; +SET client_min_messages TO DEFAULT; +-- pick some example values that reside on the shards locally and remote +-- distribution key values of 1,6, 500 and 701 are LOCAL to shards, +-- we'll use these values in the tests +SELECT shard_of_distribution_column_is_local(1); + shard_of_distribution_column_is_local +--------------------------------------------------------------------- + t +(1 row) + +SELECT shard_of_distribution_column_is_local(6); + shard_of_distribution_column_is_local +--------------------------------------------------------------------- + t +(1 row) + +SELECT shard_of_distribution_column_is_local(500); + shard_of_distribution_column_is_local +--------------------------------------------------------------------- + t +(1 row) + +SELECT shard_of_distribution_column_is_local(701); + shard_of_distribution_column_is_local +--------------------------------------------------------------------- + t +(1 row) + +-- distribution key values of 11 and 12 are REMOTE to shards +SELECT shard_of_distribution_column_is_local(11); + shard_of_distribution_column_is_local +--------------------------------------------------------------------- + f +(1 row) + +SELECT shard_of_distribution_column_is_local(12); + shard_of_distribution_column_is_local +--------------------------------------------------------------------- + f +(1 row) + +--- enable logging to see which tasks are executed locally +SET citus.log_local_commands TO ON; +-- first, make sure that local execution works fine +-- with simple queries that are not in transcation blocks +SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- multiple tasks both of which are local should NOT use local execution +-- because local execution means executing the tasks locally, so the executor +-- favors parallel execution even if everyting is local to node +SELECT count(*) FROM distributed_table WHERE key IN (1,6); + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- queries that hit any remote shards should NOT use local execution +SELECT count(*) FROM distributed_table WHERE key IN (1,11); + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM distributed_table; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- modifications also follow the same rules +INSERT INTO reference_table VALUES (1) ON CONFLICT DO NOTHING; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 AS citus_table_alias (key) VALUES (1) ON CONFLICT DO NOTHING +INSERT INTO distributed_table VALUES (1, '1', 21) ON CONFLICT DO NOTHING; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '1'::text, 21) ON CONFLICT DO NOTHING +-- local query +DELETE FROM distributed_table WHERE key = 1 AND age = 21; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((key OPERATOR(pg_catalog.=) 1) AND (age OPERATOR(pg_catalog.=) 21)) +-- hitting multiple shards, so should be a distributed execution +DELETE FROM distributed_table WHERE age = 21; +-- although both shards are local, the executor choose the parallel execution +-- over the wire because as noted above local execution is sequential +DELETE FROM second_distributed_table WHERE key IN (1,6); +-- similarly, any multi-shard query just follows distributed execution +DELETE FROM second_distributed_table; +-- load some more data for the following tests +INSERT INTO second_distributed_table VALUES (1, '1'); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.second_distributed_table_1470005 (key, value) VALUES (1, '1'::text) +-- INSERT .. SELECT hitting a single single (co-located) shard(s) should +-- be executed locally +INSERT INTO distributed_table +SELECT + distributed_table.* +FROM + distributed_table, second_distributed_table +WHERE + distributed_table.key = 1 and distributed_table.key=second_distributed_table.key +ON CONFLICT(key) DO UPDATE SET value = '22' +RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution.distributed_table_1470001 distributed_table, local_shard_execution.second_distributed_table_1470005 second_distributed_table WHERE (((distributed_table.key OPERATOR(pg_catalog.=) 1) AND (distributed_table.key OPERATOR(pg_catalog.=) second_distributed_table.key)) AND (distributed_table.key IS NOT NULL)) ON CONFLICT(key) DO UPDATE SET value = '22'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 22 | 20 +(1 row) + +-- INSERT .. SELECT hitting multi-shards should go thourgh distributed execution +INSERT INTO distributed_table +SELECT + distributed_table.* +FROM + distributed_table, second_distributed_table +WHERE + distributed_table.key != 1 and distributed_table.key=second_distributed_table.key +ON CONFLICT(key) DO UPDATE SET value = '22' +RETURNING *; + key | value | age +--------------------------------------------------------------------- +(0 rows) + +-- INSERT..SELECT via coordinator consists of two steps, select + COPY +-- that's why it is disallowed to use local execution even if the SELECT +-- can be executed locally +INSERT INTO distributed_table SELECT * FROM distributed_table WHERE key = 1 OFFSET 0 ON CONFLICT DO NOTHING; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) OFFSET 0 +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) SELECT key, value, age FROM read_intermediate_result('insert_select_XXX_1470001'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint) ON CONFLICT DO NOTHING +INSERT INTO distributed_table SELECT 1, '1',15 FROM distributed_table WHERE key = 2 LIMIT 1 ON CONFLICT DO NOTHING; +-- sanity check: multi-shard INSERT..SELECT pushdown goes through distributed execution +INSERT INTO distributed_table SELECT * FROM distributed_table ON CONFLICT DO NOTHING; +-- Ensure tuple data in explain analyze output is the same on all PG versions +SET citus.enable_binary_protocol = TRUE; +-- EXPLAIN for local execution just works fine +-- though going through distributed execution +EXPLAIN (COSTS OFF) SELECT * FROM distributed_table WHERE key = 1 AND age = 20; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Index Scan using distributed_table_pkey_1470001 on distributed_table_1470001 distributed_table + Index Cond: (key = 1) + Filter: (age = 20) +(8 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM distributed_table WHERE key = 1 AND age = 20; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) (actual rows=1 loops=1) + Task Count: 1 + Tuple data received from nodes: 14 bytes + Tasks Shown: All + -> Task + Tuple data received from node: 14 bytes + Node: host=localhost port=xxxxx dbname=regression + -> Index Scan using distributed_table_pkey_1470001 on distributed_table_1470001 distributed_table (actual rows=1 loops=1) + Index Cond: (key = 1) + Filter: (age = 20) +(10 rows) + +EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF) +WITH r AS ( SELECT GREATEST(random(), 2) z,* FROM distributed_table) +SELECT 1 FROM r WHERE z < 3; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) (actual rows=1 loops=1) + -> Distributed Subplan XXX_1 + Intermediate Data Size: 40 bytes + Result destination: Write locally + -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) + Task Count: 4 + Tuple data received from nodes: 22 bytes + Tasks Shown: One of 4 + -> Task + Tuple data received from node: 22 bytes + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on distributed_table_1470001 distributed_table (actual rows=1 loops=1) + Task Count: 1 + Tuple data received from nodes: 4 bytes + Tasks Shown: All + -> Task + Tuple data received from node: 4 bytes + Node: host=localhost port=xxxxx dbname=regression + -> Function Scan on read_intermediate_result intermediate_result (actual rows=1 loops=1) + Filter: (z < '3'::double precision) +(20 rows) + +EXPLAIN (COSTS OFF) DELETE FROM distributed_table WHERE key = 1 AND age = 20; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Delete on distributed_table_1470001 distributed_table + -> Index Scan using distributed_table_pkey_1470001 on distributed_table_1470001 distributed_table + Index Cond: (key = 1) + Filter: (age = 20) +(9 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) DELETE FROM distributed_table WHERE key = 1 AND age = 20; + 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 + -> Delete on distributed_table_1470001 distributed_table (actual rows=0 loops=1) + -> Index Scan using distributed_table_pkey_1470001 on distributed_table_1470001 distributed_table (actual rows=1 loops=1) + Index Cond: (key = 1) + Filter: (age = 20) + Trigger for constraint second_distributed_table_key_fkey_1470005: calls=1 +(10 rows) + +-- show that EXPLAIN ANALYZE deleted the row and cascades deletes +SELECT * FROM distributed_table WHERE key = 1 AND age = 20 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE ((key OPERATOR(pg_catalog.=) 1) AND (age OPERATOR(pg_catalog.=) 20)) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM second_distributed_table WHERE key = 1 ORDER BY 1,2; +NOTICE: executing the command locally: SELECT key, value FROM local_shard_execution.second_distributed_table_1470005 second_distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value + key | value +--------------------------------------------------------------------- +(0 rows) + +-- Put rows back for other tests +INSERT INTO distributed_table VALUES (1, '22', 20); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 (key, value, age) VALUES (1, '22'::text, 20) +INSERT INTO second_distributed_table VALUES (1, '1'); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.second_distributed_table_1470005 (key, value) VALUES (1, '1'::text) +SET citus.enable_ddl_propagation TO OFF; +CREATE VIEW abcd_view AS SELECT * FROM abcd; +RESET citus.enable_ddl_propagation; +SELECT * FROM abcd first join abcd second on first.b = second.b ORDER BY 1,2,3,4; + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +BEGIN; +SELECT * FROM abcd first join abcd second on first.b = second.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.b, second.c, second.d FROM (local_shard_execution.abcd_1470025 first JOIN local_shard_execution.abcd_1470025 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.b, second.c, second.d FROM (local_shard_execution.abcd_1470027 first JOIN local_shard_execution.abcd_1470027 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd_view first join abcd_view second on first.b = second.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT abcd.b, abcd.c, abcd.d, abcd_1.b, abcd_1.c, abcd_1.d FROM (local_shard_execution.abcd_1470025 abcd JOIN local_shard_execution.abcd_1470025 abcd_1 ON ((abcd.b OPERATOR(pg_catalog.=) abcd_1.b))) WHERE true +NOTICE: executing the command locally: SELECT abcd.b, abcd.c, abcd.d, abcd_1.b, abcd_1.c, abcd_1.d FROM (local_shard_execution.abcd_1470027 abcd JOIN local_shard_execution.abcd_1470027 abcd_1 ON ((abcd.b OPERATOR(pg_catalog.=) abcd_1.b))) WHERE true + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd first full join abcd second on first.b = second.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT worker_column_1 AS b, worker_column_2 AS c, worker_column_3 AS d, worker_column_4 AS b, worker_column_5 AS c, worker_column_6 AS d FROM (SELECT first.b AS worker_column_1, first.c AS worker_column_2, first.d AS worker_column_3, second.b AS worker_column_4, second.c AS worker_column_5, second.d AS worker_column_6 FROM (local_shard_execution.abcd_1470025 first FULL JOIN local_shard_execution.abcd_1470025 second ON ((first.b OPERATOR(pg_catalog.=) second.b)))) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS b, worker_column_2 AS c, worker_column_3 AS d, worker_column_4 AS b, worker_column_5 AS c, worker_column_6 AS d FROM (SELECT first.b AS worker_column_1, first.c AS worker_column_2, first.d AS worker_column_3, second.b AS worker_column_4, second.c AS worker_column_5, second.d AS worker_column_6 FROM (local_shard_execution.abcd_1470027 first FULL JOIN local_shard_execution.abcd_1470027 second ON ((first.b OPERATOR(pg_catalog.=) second.b)))) worker_subquery + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd first join abcd second USING(b) ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d FROM (local_shard_execution.abcd_1470025 first JOIN local_shard_execution.abcd_1470025 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d FROM (local_shard_execution.abcd_1470027 first JOIN local_shard_execution.abcd_1470027 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true + b | c | d | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 3 | 4 + 3 | 4 | 5 | 4 | 5 + 4 | 5 | 6 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd first join abcd second USING(b) join abcd third on first.b=third.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d, third.b, third.c, third.d FROM ((local_shard_execution.abcd_1470025 first JOIN local_shard_execution.abcd_1470025 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) JOIN local_shard_execution.abcd_1470025 third ON ((first.b OPERATOR(pg_catalog.=) third.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d, third.b, third.c, third.d FROM ((local_shard_execution.abcd_1470027 first JOIN local_shard_execution.abcd_1470027 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) JOIN local_shard_execution.abcd_1470027 third ON ((first.b OPERATOR(pg_catalog.=) third.b))) WHERE true + b | c | d | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +-- copy always happens via distributed execution irrespective of the +-- shards that are accessed +COPY reference_table FROM STDIN; +COPY distributed_table FROM STDIN WITH CSV; +COPY second_distributed_table FROM STDIN WITH CSV; +-- the behaviour in transaction blocks is the following: + -- (a) Unless the first query is a local query, always use distributed execution. + -- (b) If the executor has used local execution, it has to use local execution + -- for the remaining of the transaction block. If that's not possible, the + -- executor has to error out +-- rollback should be able to rollback local execution +BEGIN; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 29 | 20 +(1 row) + + SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 29 | 20 +(1 row) + +ROLLBACK; +-- make sure that the value is rollbacked +SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 22 | 20 +(1 row) + +-- rollback should be able to rollback both the local and distributed executions +BEGIN; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 29 | 20 +(1 row) + + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table + -- DELETE should cascade, and we should not see any rows + SELECT count(*) FROM second_distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.second_distributed_table_1470005 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.second_distributed_table_1470007 second_distributed_table WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + +ROLLBACK; +-- make sure that everything is rollbacked +SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 22 | 20 +(1 row) + +SELECT count(*) FROM second_distributed_table; + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT * FROM second_distributed_table; + key | value +--------------------------------------------------------------------- + 1 | 1 + 6 | '6' +(2 rows) + +-- very simple examples, an SELECTs should see the modifications +-- that has done before +BEGIN; + -- INSERT is executed locally + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '23' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '23'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 23 | 20 +(1 row) + + -- since the INSERT is executed locally, the SELECT should also be + -- executed locally and see the changes + SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 23 | 20 +(1 row) + + -- multi-shard SELECTs are now forced to use local execution on + -- the shards that reside on this node + SELECT * FROM distributed_table WHERE value = '23' ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) + key | value | age +--------------------------------------------------------------------- + 1 | 23 | 20 +(1 row) + + -- similarly, multi-shard modifications should use local exection + -- on the shards that reside on this node + DELETE FROM distributed_table WHERE value = '23'; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) + -- make sure that the value is deleted + SELECT * FROM distributed_table WHERE value = '23' ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) + key | value | age +--------------------------------------------------------------------- +(0 rows) + +COMMIT; +-- make sure that we've committed everything +SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- +(0 rows) + +-- if we start with a distributed execution, we should keep +-- using that and never switch back to local execution +BEGIN; + DELETE FROM distributed_table WHERE value = '11'; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '11'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '11'::text) + -- although this command could have been executed + -- locally, it is not going to be executed locally + SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- +(0 rows) + + -- but we can still execute parallel queries, even if + -- they are utility commands + TRUNCATE distributed_table CASCADE; +NOTICE: truncate cascades to table "second_distributed_table" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE + -- TRUNCATE cascaded into second_distributed_table + SELECT count(*) FROM second_distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.second_distributed_table_1470005 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.second_distributed_table_1470007 second_distributed_table WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + +ROLLBACK; +-- load some data so that foreign keys won't complain with the next tests +INSERT INTO reference_table SELECT i FROM generate_series(500, 600) i; +NOTICE: executing the copy locally for shard xxxxx +-- show that cascading foreign keys just works fine with local execution +BEGIN; + INSERT INTO reference_table VALUES (701); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 (key) VALUES (701) + INSERT INTO distributed_table VALUES (701, '701', 701); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 (key, value, age) VALUES (701, '701'::text, 701) + INSERT INTO second_distributed_table VALUES (701, '701'); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.second_distributed_table_1470005 (key, value) VALUES (701, '701'::text) + DELETE FROM reference_table WHERE key = 701; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.reference_table_1470000 reference_table WHERE (key OPERATOR(pg_catalog.=) 701) + SELECT count(*) FROM distributed_table WHERE key = 701; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 701) + count +--------------------------------------------------------------------- + 0 +(1 row) + + SELECT count(*) FROM second_distributed_table WHERE key = 701; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.second_distributed_table_1470005 second_distributed_table WHERE (key OPERATOR(pg_catalog.=) 701) + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- multi-shard commands should also see the changes + SELECT count(*) FROM distributed_table WHERE key > 700; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.>) 700) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.>) 700) + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- we can still do multi-shard commands + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table +ROLLBACK; +-- multiple queries hitting different shards can be executed locally +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + + SELECT count(*) FROM distributed_table WHERE key = 6; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 1 +(1 row) + + SELECT count(*) FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + count +--------------------------------------------------------------------- + 0 +(1 row) + +ROLLBACK; +-- a local query followed by TRUNCATE command can be executed locally +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + + TRUNCATE distributed_table CASCADE; +NOTICE: truncate cascades to table "second_distributed_table" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +ROLLBACK; +-- a local query is followed by an INSERT..SELECT via the coordinator +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + + INSERT INTO distributed_table (key) SELECT i FROM generate_series(1,1) i; +NOTICE: executing the copy locally for shard xxxxx +ROLLBACK; +BEGIN; +SET citus.enable_repartition_joins TO ON; +SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM distributed_table d1 join distributed_table d2 using(age); +NOTICE: executing the command locally: SELECT partition_index, 'repartition_65_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_65_1','SELECT age AS column1 FROM local_shard_execution.distributed_table_1470001 d1 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_65_3' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_65_3','SELECT age AS column1 FROM local_shard_execution.distributed_table_1470003 d1 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_66_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_66_1','SELECT age AS column1 FROM local_shard_execution.distributed_table_1470001 d2 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_66_3' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_66_3','SELECT age AS column1 FROM local_shard_execution.distributed_table_1470003 d2 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_1_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_2_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_3_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_4_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_1_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_2_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_3_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_4_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_1_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_2_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_3_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_4_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_1_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_2_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_3_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_66_4_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_65_1_0,repartition_65_2_0,repartition_65_3_0,repartition_65_4_0}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_66_1_0,repartition_66_2_0,repartition_66_3_0,repartition_66_4_0}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_65_1_1,repartition_65_2_1,repartition_65_3_1,repartition_65_4_1}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_66_1_1,repartition_66_2_1,repartition_66_3_1,repartition_66_4_1}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_65_1_2,repartition_65_2_2,repartition_65_3_2,repartition_65_4_2}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_66_1_2,repartition_66_2_2,repartition_66_3_2,repartition_66_4_2}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_65_1_3,repartition_65_2_3,repartition_65_3_3,repartition_65_4_3}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_66_1_3,repartition_66_2_3,repartition_66_3_3,repartition_66_4_3}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + +ROLLBACK; +-- a local query is followed by an INSERT..SELECT with re-partitioning +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 6; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 1 +(1 row) + + INSERT INTO reference_table (key) SELECT -key FROM distributed_table; +NOTICE: executing the command locally: SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true +NOTICE: executing the copy locally for shard xxxxx + INSERT INTO distributed_table (key) SELECT -key FROM distributed_table; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1470001_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1470001_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1470003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1470003_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key) SELECT key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1470003_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) + SELECT count(*) FROM distributed_table WHERE key = -6; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) '-6'::integer) + count +--------------------------------------------------------------------- + 1 +(1 row) + +ROLLBACK; +INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 11 | 21 +(1 row) + +BEGIN; + DELETE FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + EXPLAIN ANALYZE DELETE FROM distributed_table WHERE key = 1; +ERROR: cannot execute command because a local execution has accessed a placement in the transaction +DETAIL: Some parallel commands cannot be executed if a previous command has already been executed locally +HINT: Try re-running the transaction with "SET LOCAL citus.enable_local_execution TO OFF;" +ROLLBACK; +BEGIN; + INSERT INTO distributed_table VALUES (11, '111',29) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; + key | value | age +--------------------------------------------------------------------- + 11 | 29 | 121 +(1 row) + + -- this is already disallowed on the nodes, adding it in case we + -- support DDLs from the worker nodes in the future + ALTER TABLE distributed_table ADD COLUMN x INT; +ERROR: operation is not allowed on this node +HINT: Connect to the coordinator and run it again. +ROLLBACK; +BEGIN; + INSERT INTO distributed_table VALUES (11, '111',29) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; + key | value | age +--------------------------------------------------------------------- + 11 | 29 | 121 +(1 row) + + -- this is already disallowed because VACUUM cannot be executed in tx block + -- adding in case this is supported some day + VACUUM second_distributed_table; +ERROR: VACUUM cannot run inside a transaction block +ROLLBACK; +-- make sure that functions can use local execution +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE PROCEDURE only_local_execution() AS $$ + DECLARE cnt INT; + BEGIN + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; + SELECT count(*) INTO cnt FROM distributed_table WHERE key = 1; + DELETE FROM distributed_table WHERE key = 1; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution(); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text +CONTEXT: SQL statement "INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'" +PL/pgSQL function only_local_execution() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table WHERE key = 1" +PL/pgSQL function only_local_execution() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "DELETE FROM distributed_table WHERE key = 1" +PL/pgSQL function only_local_execution() line XX at SQL statement +-- insert a row that we need in the next tests +INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text +-- make sure that functions can use local execution +CREATE OR REPLACE PROCEDURE only_local_execution_with_function_evaluation() AS $$ + DECLARE nodeId INT; + BEGIN + -- fast path router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table WHERE key = 1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + + -- regular router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = 1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution_with_function_evaluation(); +NOTICE: executing the command locally: SELECT local_shard_execution.get_local_node_id_volatile() AS get_local_node_id_volatile FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table WHERE key = 1" +PL/pgSQL function only_local_execution_with_function_evaluation() line XX at SQL statement +NOTICE: executing the command locally: SELECT local_shard_execution.get_local_node_id_volatile() AS get_local_node_id_volatile FROM (local_shard_execution.distributed_table_1470001 d1(key, value, age) JOIN local_shard_execution.distributed_table_1470001 d2(key, value, age) USING (key)) WHERE (d1.key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = 1" +PL/pgSQL function only_local_execution_with_function_evaluation() line XX at SQL statement +CREATE OR REPLACE PROCEDURE only_local_execution_with_params(int) AS $$ + DECLARE cnt INT; + BEGIN + INSERT INTO distributed_table VALUES ($1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; + SELECT count(*) INTO cnt FROM distributed_table WHERE key = $1; + DELETE FROM distributed_table WHERE key = $1; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution_with_params(1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '29'::text +CONTEXT: SQL statement "INSERT INTO distributed_table VALUES ($1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'" +PL/pgSQL function only_local_execution_with_params(integer) line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table WHERE key = $1" +PL/pgSQL function only_local_execution_with_params(integer) line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "DELETE FROM distributed_table WHERE key = $1" +PL/pgSQL function only_local_execution_with_params(integer) line XX at SQL statement +CREATE OR REPLACE PROCEDURE only_local_execution_with_function_evaluation_param(int) AS $$ + DECLARE nodeId INT; + BEGIN + -- fast path router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table WHERE key = $1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + + -- regular router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = $1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution_with_function_evaluation_param(1); +NOTICE: executing the command locally: SELECT local_shard_execution.get_local_node_id_volatile() AS get_local_node_id_volatile FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table WHERE key = $1" +PL/pgSQL function only_local_execution_with_function_evaluation_param(integer) line XX at SQL statement +NOTICE: executing the command locally: SELECT local_shard_execution.get_local_node_id_volatile() AS get_local_node_id_volatile FROM (local_shard_execution.distributed_table_1470001 d1(key, value, age) JOIN local_shard_execution.distributed_table_1470001 d2(key, value, age) USING (key)) WHERE (d1.key OPERATOR(pg_catalog.=) $1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = $1" +PL/pgSQL function only_local_execution_with_function_evaluation_param(integer) line XX at SQL statement +CREATE OR REPLACE PROCEDURE local_execution_followed_by_dist() AS $$ + DECLARE cnt INT; + BEGIN + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; + SELECT count(*) INTO cnt FROM distributed_table WHERE key = 1; + DELETE FROM distributed_table; + SELECT count(*) INTO cnt FROM distributed_table; + END; +$$ LANGUAGE plpgsql; +RESET citus.enable_metadata_sync; +CALL local_execution_followed_by_dist(); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text +CONTEXT: SQL statement "INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table WHERE key = 1" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table +CONTEXT: SQL statement "DELETE FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table +CONTEXT: SQL statement "DELETE FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +-- test CTEs, including modifying CTEs +WITH local_insert AS (INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *), +distributed_local_mixed AS (SELECT * FROM reference_table WHERE key IN (SELECT key FROM local_insert)) +SELECT * FROM local_insert, distributed_local_mixed; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age +NOTICE: executing the command locally: SELECT key FROM local_shard_execution.reference_table_1470000 reference_table WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT local_insert.key FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert)) +NOTICE: executing the command locally: SELECT local_insert.key, local_insert.value, local_insert.age, distributed_local_mixed.key FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) distributed_local_mixed + key | value | age | key +--------------------------------------------------------------------- + 1 | 11 | 21 | 1 +(1 row) + +-- since we start with parallel execution, we do not switch back to local execution in the +-- latter CTEs +WITH distributed_local_mixed AS (SELECT * FROM distributed_table), +local_insert AS (INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *) +SELECT * FROM local_insert, distributed_local_mixed ORDER BY 1,2,3,4,5; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age +NOTICE: executing the command locally: SELECT worker_column_1 AS key, worker_column_2 AS value, worker_column_3 AS age, worker_column_4 AS key, worker_column_5 AS value, worker_column_6 AS age FROM (SELECT local_insert.key AS worker_column_1, local_insert.value AS worker_column_2, local_insert.age AS worker_column_3, distributed_local_mixed.key AS worker_column_4, distributed_local_mixed.value AS worker_column_5, distributed_local_mixed.age AS worker_column_6 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution.distributed_table_1470001 distributed_table) distributed_local_mixed) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS key, worker_column_2 AS value, worker_column_3 AS age, worker_column_4 AS key, worker_column_5 AS value, worker_column_6 AS age FROM (SELECT local_insert.key AS worker_column_1, local_insert.value AS worker_column_2, local_insert.age AS worker_column_3, distributed_local_mixed.key AS worker_column_4, distributed_local_mixed.value AS worker_column_5, distributed_local_mixed.age AS worker_column_6 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution.distributed_table_1470003 distributed_table) distributed_local_mixed) worker_subquery + key | value | age | key | value | age +--------------------------------------------------------------------- + 1 | 29 | 21 | 1 | 11 | 21 +(1 row) + +-- router CTE pushdown +WITH all_data AS (SELECT * FROM distributed_table WHERE key = 1) +SELECT + count(*) +FROM + distributed_table, all_data +WHERE + distributed_table.key = all_data.key AND distributed_table.key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table, (SELECT distributed_table_1.key, distributed_table_1.value, distributed_table_1.age FROM local_shard_execution.distributed_table_1470001 distributed_table_1 WHERE (distributed_table_1.key OPERATOR(pg_catalog.=) 1)) all_data WHERE ((distributed_table.key OPERATOR(pg_catalog.=) all_data.key) AND (distributed_table.key OPERATOR(pg_catalog.=) 1)) + count +--------------------------------------------------------------------- + 1 +(1 row) + +INSERT INTO reference_table VALUES (2); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 (key) VALUES (2) +INSERT INTO distributed_table VALUES (2, '29', 29); +INSERT INTO second_distributed_table VALUES (2, '29'); +-- single shard that is not a local query followed by a local query +WITH all_data AS (SELECT * FROM second_distributed_table WHERE key = 2) +SELECT + distributed_table.key +FROM + distributed_table, all_data +WHERE + distributed_table.value = all_data.value AND distributed_table.key = 1 +ORDER BY + 1 DESC; +NOTICE: executing the command locally: SELECT distributed_table.key FROM local_shard_execution.distributed_table_1470001 distributed_table, (SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text)) all_data WHERE ((distributed_table.value OPERATOR(pg_catalog.=) all_data.value) AND (distributed_table.key OPERATOR(pg_catalog.=) 1)) ORDER BY distributed_table.key DESC + key +--------------------------------------------------------------------- + 1 +(1 row) + +-- multi-shard CTE is followed by a query which could be executed locally, but +-- since the query started with a parallel query, it doesn't use local execution +-- note that if we allow Postgres to inline the CTE (e.g., not have the EXISTS +-- subquery), then it'd pushdown the filters and the query becomes single-shard, +-- locally executable query +WITH all_data AS (SELECT * FROM distributed_table) +SELECT + count(*) +FROM + distributed_table, all_data +WHERE + distributed_table.key = all_data.key AND distributed_table.key = 1 + AND EXISTS (SELECT * FROM all_data); +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table, (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) all_data WHERE ((distributed_table.key OPERATOR(pg_catalog.=) all_data.key) AND (distributed_table.key OPERATOR(pg_catalog.=) 1) AND (EXISTS (SELECT all_data_1.key, all_data_1.value, all_data_1.age FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) all_data_1))) + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- in pg12, the following CTE can be inlined, still the query becomes +-- a subquery that needs to be recursively planned and a parallel +-- query, so do not use local execution +WITH all_data AS (SELECT age FROM distributed_table) +SELECT + count(*) +FROM + distributed_table, all_data +WHERE + distributed_table.key = all_data.age AND distributed_table.key = 1; +NOTICE: executing the command locally: SELECT age FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT age FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table, (SELECT intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(age bigint)) all_data WHERE ((distributed_table.key OPERATOR(pg_catalog.=) all_data.age) AND (distributed_table.key OPERATOR(pg_catalog.=) 1)) + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- get ready for the next commands +TRUNCATE reference_table, distributed_table, second_distributed_table; +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.reference_table_xxxxx CASCADE +NOTICE: truncate cascades to table "distributed_table_xxxxx" +NOTICE: truncate cascades to table "distributed_table_xxxxx" +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +-- local execution of returning of reference tables +INSERT INTO reference_table VALUES (1),(2),(3),(4),(5),(6) RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 AS citus_table_alias (key) VALUES (1), (2), (3), (4), (5), (6) RETURNING citus_table_alias.key + key +--------------------------------------------------------------------- + 1 + 2 + 3 + 4 + 5 + 6 +(6 rows) + +-- local execution of multi-row INSERTs +INSERT INTO distributed_table VALUES (1, '11',21), (5,'55',22) ON CONFLICT(key) DO UPDATE SET value = (EXCLUDED.value::int + 1)::text RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'11'::text,'21'::bigint), (5,'55'::text,'22'::bigint) ON CONFLICT(key) DO UPDATE SET value = (((excluded.value)::integer OPERATOR(pg_catalog.+) 1))::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 11 | 21 + 5 | 55 | 22 +(2 rows) + +-- distributed execution of multi-rows INSERTs, where executor +-- is smart enough to execute local tasks via local execution +INSERT INTO distributed_table VALUES (1, '11',21), (2,'22',22), (3,'33',33), (4,'44',44),(5,'55',55) ON CONFLICT(key) DO UPDATE SET value = (EXCLUDED.value::int + 1)::text RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'11'::text,'21'::bigint), (5,'55'::text,'55'::bigint) ON CONFLICT(key) DO UPDATE SET value = (((excluded.value)::integer OPERATOR(pg_catalog.+) 1))::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 12 | 21 + 2 | 22 | 22 + 3 | 33 | 33 + 4 | 44 | 44 + 5 | 56 | 22 +(5 rows) + +PREPARE local_prepare_no_param AS SELECT count(*) FROM distributed_table WHERE key = 1; +PREPARE local_prepare_no_param_subquery AS +SELECT DISTINCT trim(value) FROM ( + SELECT value FROM distributed_table + WHERE + key IN (1, 6, 500, 701) + AND (select 2) > random() + order by 1 + limit 2 + ) t; +PREPARE local_prepare_param (int) AS SELECT count(*) FROM distributed_table WHERE key = $1; +PREPARE remote_prepare_param (int) AS SELECT count(*) FROM distributed_table WHERE key != $1; +BEGIN; + -- 8 local execution without params + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + -- 8 local execution without params and some subqueries + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + -- 8 local executions with params + EXECUTE local_prepare_param(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(5); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 5) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + EXECUTE local_prepare_param(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(5); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 5) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- followed by a non-local execution + EXECUTE remote_prepare_param(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 1) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 1) + count +--------------------------------------------------------------------- + 4 +(1 row) + +COMMIT; +PREPARE local_insert_prepare_no_param AS INSERT INTO distributed_table VALUES (1+0*random(), '11',21::int) ON CONFLICT(key) DO UPDATE SET value = '29' || '28' RETURNING *, key + 1, value || '30', age * 15; +PREPARE local_insert_prepare_param (int) AS INSERT INTO distributed_table VALUES ($1+0*random(), '11',21::int) ON CONFLICT(key) DO UPDATE SET value = '29' || '28' RETURNING *, key + 1, value || '30', age * 15; +BEGIN; + -- 8 local execution without params + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + -- 8 local executions with params + EXECUTE local_insert_prepare_param(1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 5 | 2928 | 22 | 6 | 292830 | 330 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 11 | 21 | 7 | 1130 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 5 | 2928 | 22 | 6 | 292830 | 330 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 2928 | 21 | 7 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 2928 | 21 | 7 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 2928 | 21 | 7 | 292830 | 315 +(1 row) + + -- followed by a non-local execution + EXECUTE remote_prepare_param(2); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 2) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 2) + count +--------------------------------------------------------------------- + 5 +(1 row) + +COMMIT; +PREPARE local_multi_row_insert_prepare_no_param AS + INSERT INTO distributed_table VALUES (1,'55', 21), (5,'15',33) ON CONFLICT (key) WHERE key > 3 and key < 4 DO UPDATE SET value = '88' || EXCLUDED.value; +PREPARE local_multi_row_insert_prepare_no_param_multi_shard AS + INSERT INTO distributed_table VALUES (6,'55', 21), (5,'15',33) ON CONFLICT (key) WHERE key > 3 AND key < 4 DO UPDATE SET value = '88' || EXCLUDED.value;; +PREPARE local_multi_row_insert_prepare_params(int,int) AS + INSERT INTO distributed_table VALUES ($1,'55', 21), ($2,'15',33) ON CONFLICT (key) WHERE key > 3 and key < 4 DO UPDATE SET value = '88' || EXCLUDED.value;; +INSERT INTO reference_table VALUES (11); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 (key) VALUES (11) +BEGIN; + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(6,5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(5,1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint), (1,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(5,6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(5,1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint), (1,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470003 AS citus_table_alias (key, value, age) VALUES (6,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + -- one task is remote + EXECUTE local_multi_row_insert_prepare_params(5,11); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +ROLLBACK; +-- make sure that we still get results if we switch off local execution +PREPARE ref_count_prepare AS SELECT count(*) FROM reference_table; +EXECUTE ref_count_prepare; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.reference_table_1470000 reference_table + count +--------------------------------------------------------------------- + 7 +(1 row) + +EXECUTE ref_count_prepare; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.reference_table_1470000 reference_table + count +--------------------------------------------------------------------- + 7 +(1 row) + +EXECUTE ref_count_prepare; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.reference_table_1470000 reference_table + count +--------------------------------------------------------------------- + 7 +(1 row) + +EXECUTE ref_count_prepare; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.reference_table_1470000 reference_table + count +--------------------------------------------------------------------- + 7 +(1 row) + +EXECUTE ref_count_prepare; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.reference_table_1470000 reference_table + count +--------------------------------------------------------------------- + 7 +(1 row) + +SET citus.enable_local_execution TO off; +EXECUTE ref_count_prepare; + count +--------------------------------------------------------------------- + 7 +(1 row) + +RESET citus.enable_local_execution; +-- failures of local execution should rollback both the +-- local execution and remote executions +-- fail on a local execution +BEGIN; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '100' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '100'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 100 | 21 +(1 row) + + UPDATE distributed_table SET value = '200'; +NOTICE: executing the command locally: UPDATE local_shard_execution.distributed_table_1470001 distributed_table SET value = '200'::text +NOTICE: executing the command locally: UPDATE local_shard_execution.distributed_table_1470003 distributed_table SET value = '200'::text + INSERT INTO distributed_table VALUES (1, '100',21) ON CONFLICT(key) DO UPDATE SET value = (1 / (100.0 - EXCLUDED.value::int))::text RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '100'::text, 21) ON CONFLICT(key) DO UPDATE SET value = (((1)::numeric OPERATOR(pg_catalog./) (100.0 OPERATOR(pg_catalog.-) ((excluded.value)::integer)::numeric)))::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age +ERROR: division by zero +ROLLBACK; +-- we've rollbacked everything +SELECT count(*) FROM distributed_table WHERE value = '200'; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- RETURNING should just work fine for reference tables +INSERT INTO reference_table VALUES (500) RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 (key) VALUES (500) RETURNING key + key +--------------------------------------------------------------------- + 500 +(1 row) + +DELETE FROM reference_table WHERE key = 500 RETURNING *; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.reference_table_1470000 reference_table WHERE (key OPERATOR(pg_catalog.=) 500) RETURNING key + key +--------------------------------------------------------------------- + 500 +(1 row) + +-- should be able to skip local execution even if in a sequential mode of execution +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO sequential ; + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '100' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '100'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 11 | 21 +(1 row) + +ROLLBACK; +-- sequential execution should just work fine after a local execution +BEGIN; + SET citus.multi_shard_modify_mode TO sequential ; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '100' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '100'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 100 | 21 +(1 row) + + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table +ROLLBACK; +-- load some data so that foreign keys won't complain with the next tests +TRUNCATE reference_table CASCADE; +NOTICE: truncate cascades to table "distributed_table" +NOTICE: truncate cascades to table "second_distributed_table" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.reference_table_xxxxx CASCADE +NOTICE: truncate cascades to table "distributed_table_xxxxx" +NOTICE: truncate cascades to table "distributed_table_xxxxx" +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +INSERT INTO reference_table SELECT i FROM generate_series(500, 600) i; +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO distributed_table SELECT i, i::text, i % 10 + 25 FROM generate_series(500, 600) i; +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +-- show that both local, and mixed local-distributed executions +-- calculate rows processed correctly +BEGIN; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + DELETE FROM distributed_table WHERE value != '123123213123213'; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE (value OPERATOR(pg_catalog.<>) '123123213123213'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (value OPERATOR(pg_catalog.<>) '123123213123213'::text) +ROLLBACK; +BEGIN; + DELETE FROM reference_table WHERE key = 500 RETURNING *; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.reference_table_1470000 reference_table WHERE (key OPERATOR(pg_catalog.=) 500) RETURNING key + key +--------------------------------------------------------------------- + 500 +(1 row) + + DELETE FROM reference_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.reference_table_1470000 reference_table +ROLLBACK; +BEGIN; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + +ROLLBACK; +BEGIN; + SET LOCAL client_min_messages TO INFO; + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true + count +--------------------------------------------------------------------- + 101 +(1 row) + + SET LOCAL client_min_messages TO LOG; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) +ROLLBACK; +-- probably not a realistic case since views are not very +-- well supported with MX +SET citus.enable_ddl_propagation TO OFF; +CREATE VIEW v_local_query_execution AS +SELECT * FROM distributed_table WHERE key = 500; +RESET citus.enable_ddl_propagation; +SELECT * FROM v_local_query_execution; +NOTICE: executing the command locally: SELECT key, value, age FROM (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (distributed_table.key OPERATOR(pg_catalog.=) 500)) v_local_query_execution + key | value | age +--------------------------------------------------------------------- + 500 | 500 | 25 +(1 row) + +-- similar test, but this time the view itself is a non-local +-- query, but the query on the view is local +SET citus.enable_ddl_propagation TO OFF; +CREATE VIEW v_local_query_execution_2 AS +SELECT * FROM distributed_table; +RESET citus.enable_ddl_propagation; +SELECT * FROM v_local_query_execution_2 WHERE key = 500; +NOTICE: executing the command locally: SELECT key, value, age FROM (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution.distributed_table_1470003 distributed_table) v_local_query_execution_2 WHERE (key OPERATOR(pg_catalog.=) 500) + key | value | age +--------------------------------------------------------------------- + 500 | 500 | 25 +(1 row) + +-- even if we switch from remote execution -> local execution, +-- we are able to use remote execution after rollback +BEGIN; + SAVEPOINT my_savepoint; + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true + count +--------------------------------------------------------------------- + 101 +(1 row) + + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + ROLLBACK TO SAVEPOINT my_savepoint; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) +COMMIT; +-- even if we switch from local execution -> remote execution, +-- we are able to use local execution after rollback +BEGIN; + SAVEPOINT my_savepoint; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE true + count +--------------------------------------------------------------------- + 100 +(1 row) + + ROLLBACK TO SAVEPOINT my_savepoint; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) +COMMIT; +-- sanity check: local execution on partitions +INSERT INTO collections_list (collection_id) VALUES (0) RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470011 (key, ser, collection_id) VALUES ('3940649673949185'::bigint, '3940649673949185'::bigint, 0) RETURNING key, ser, ts, collection_id, value + key | ser | ts | collection_id | value +--------------------------------------------------------------------- + 3940649673949185 | 3940649673949185 | | 0 | +(1 row) + +BEGIN; + INSERT INTO collections_list (key, collection_id) VALUES (1,0); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470009 (key, ser, collection_id) VALUES ('1'::bigint, '3940649673949186'::bigint, 0) + SELECT count(*) FROM collections_list_0 WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.collections_list_0_1470013 collections_list_0 WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + SELECT count(*) FROM collections_list; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.collections_list_1470009 collections_list WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution.collections_list_1470011 collections_list WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + + SELECT * FROM collections_list ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT key, ser, ts, collection_id, value FROM local_shard_execution.collections_list_1470009 collections_list WHERE true +NOTICE: executing the command locally: SELECT key, ser, ts, collection_id, value FROM local_shard_execution.collections_list_1470011 collections_list WHERE true + key | ser | ts | collection_id | value +--------------------------------------------------------------------- + 1 | 3940649673949186 | | 0 | + 3940649673949185 | 3940649673949185 | | 0 | +(2 rows) + +COMMIT; +TRUNCATE collections_list; +-- make sure that even if local execution is used, the sequence values +-- are generated locally +SET citus.enable_ddl_propagation TO OFF; +ALTER SEQUENCE collections_list_key_seq NO MINVALUE NO MAXVALUE; +RESET citus.enable_ddl_propagation; +PREPARE serial_prepared_local AS INSERT INTO collections_list (collection_id) VALUES (0) RETURNING key, ser; +SELECT setval('collections_list_key_seq', 4); + setval +--------------------------------------------------------------------- + 4 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470009 (key, ser, collection_id) VALUES ('5'::bigint, '3940649673949187'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 5 | 3940649673949187 +(1 row) + +SELECT setval('collections_list_key_seq', 5); + setval +--------------------------------------------------------------------- + 5 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470011 (key, ser, collection_id) VALUES ('6'::bigint, '3940649673949188'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 6 | 3940649673949188 +(1 row) + +SELECT setval('collections_list_key_seq', 499); + setval +--------------------------------------------------------------------- + 499 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470011 (key, ser, collection_id) VALUES ('500'::bigint, '3940649673949189'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 500 | 3940649673949189 +(1 row) + +SELECT setval('collections_list_key_seq', 700); + setval +--------------------------------------------------------------------- + 700 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470009 (key, ser, collection_id) VALUES ('701'::bigint, '3940649673949190'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 701 | 3940649673949190 +(1 row) + +SELECT setval('collections_list_key_seq', 708); + setval +--------------------------------------------------------------------- + 708 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470011 (key, ser, collection_id) VALUES ('709'::bigint, '3940649673949191'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 709 | 3940649673949191 +(1 row) + +SELECT setval('collections_list_key_seq', 709); + setval +--------------------------------------------------------------------- + 709 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470009 (key, ser, collection_id) VALUES ('710'::bigint, '3940649673949192'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 710 | 3940649673949192 +(1 row) + +-- get ready for the next executions +DELETE FROM collections_list WHERE key IN (5,6); +SELECT setval('collections_list_key_seq', 4); + setval +--------------------------------------------------------------------- + 4 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470009 (key, ser, collection_id) VALUES ('5'::bigint, '3940649673949193'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 5 | 3940649673949193 +(1 row) + +SELECT setval('collections_list_key_seq', 5); + setval +--------------------------------------------------------------------- + 5 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.collections_list_1470011 (key, ser, collection_id) VALUES ('6'::bigint, '3940649673949194'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 6 | 3940649673949194 +(1 row) + +-- and, one remote test +SELECT setval('collections_list_key_seq', 10); + setval +--------------------------------------------------------------------- + 10 +(1 row) + +EXECUTE serial_prepared_local; + key | ser +--------------------------------------------------------------------- + 11 | 3940649673949195 +(1 row) + +-- the final queries for the following CTEs are going to happen on the intermediate results only +-- one of them will be executed remotely, and the other is locally +-- Citus currently doesn't allow using task_assignment_policy for intermediate results +WITH distributed_local_mixed AS (INSERT INTO reference_table VALUES (1000) RETURNING *) SELECT * FROM distributed_local_mixed; +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 (key) VALUES (1000) RETURNING key +NOTICE: executing the command locally: SELECT key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) distributed_local_mixed + key +--------------------------------------------------------------------- + 1000 +(1 row) + +-- clean the table for the next tests +SET search_path TO local_shard_execution; +TRUNCATE distributed_table CASCADE; +NOTICE: truncate cascades to table "second_distributed_table" +-- load some data on a remote shard +INSERT INTO reference_table (key) VALUES (1), (2); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 AS citus_table_alias (key) VALUES (1), (2) +INSERT INTO distributed_table (key) VALUES (2); +BEGIN; + -- local execution followed by a distributed query + INSERT INTO distributed_table (key) VALUES (1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.distributed_table_1470001 (key) VALUES (1) + DELETE FROM distributed_table RETURNING key; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470001 distributed_table RETURNING key +NOTICE: executing the command locally: DELETE FROM local_shard_execution.distributed_table_1470003 distributed_table RETURNING key + key +--------------------------------------------------------------------- + 1 + 2 +(2 rows) + +COMMIT; +-- a similar test with a reference table +TRUNCATE reference_table CASCADE; +NOTICE: truncate cascades to table "distributed_table" +NOTICE: truncate cascades to table "second_distributed_table" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.reference_table_xxxxx CASCADE +NOTICE: truncate cascades to table "distributed_table_xxxxx" +NOTICE: truncate cascades to table "distributed_table_xxxxx" +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.distributed_table_xxxxx CASCADE +NOTICE: truncate cascades to table "second_distributed_table_xxxxx" +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution.second_distributed_table_xxxxx CASCADE +-- load some data on a remote shard +INSERT INTO reference_table (key) VALUES (2); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 (key) VALUES (2) +BEGIN; + -- local execution followed by a distributed query + INSERT INTO reference_table (key) VALUES (1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution.reference_table_1470000 (key) VALUES (1) + DELETE FROM reference_table RETURNING key; +NOTICE: executing the command locally: DELETE FROM local_shard_execution.reference_table_1470000 reference_table RETURNING key + key +--------------------------------------------------------------------- + 1 + 2 +(2 rows) + +COMMIT; +-- however complex the query, local execution can handle +SET client_min_messages TO LOG; +SET citus.log_local_commands TO ON; +WITH cte_1 AS + (SELECT * + FROM + (WITH cte_1 AS + (SELECT * + FROM distributed_table + WHERE key = 1) SELECT * + FROM cte_1) AS foo) +SELECT count(*) +FROM cte_1 +JOIN distributed_table USING (key) +WHERE distributed_table.key = 1 + AND distributed_table.key IN + (SELECT key + FROM distributed_table + WHERE key = 1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM ((SELECT foo.key, foo.value, foo.age FROM (SELECT cte_1_1.key, cte_1_1.value, cte_1_1.age FROM (SELECT distributed_table_1.key, distributed_table_1.value, distributed_table_1.age FROM local_shard_execution.distributed_table_1470001 distributed_table_1 WHERE (distributed_table_1.key OPERATOR(pg_catalog.=) 1)) cte_1_1) foo) cte_1 JOIN local_shard_execution.distributed_table_1470001 distributed_table(key, value, age) USING (key)) WHERE ((distributed_table.key OPERATOR(pg_catalog.=) 1) AND (distributed_table.key OPERATOR(pg_catalog.=) ANY (SELECT distributed_table_1.key FROM local_shard_execution.distributed_table_1470001 distributed_table_1 WHERE (distributed_table_1.key OPERATOR(pg_catalog.=) 1)))) + count +--------------------------------------------------------------------- + 0 +(1 row) + +RESET client_min_messages; +RESET citus.log_local_commands; +\c - - - :master_port +SET citus.next_shard_id TO 1480000; +-- test both local and remote execution with custom type +SET citus.shard_replication_factor TO 1; +CREATE TYPE invite_resp AS ENUM ('yes', 'no', 'maybe'); +CREATE TABLE event_responses ( + event_id int, + user_id int, + response invite_resp, + primary key (event_id, user_id) +); +SELECT create_distributed_table('event_responses', 'event_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO event_responses VALUES (1, 1, 'yes'), (2, 2, 'yes'), (3, 3, 'no'), (4, 4, 'no'); +CREATE TABLE event_responses_no_pkey ( + event_id int, + user_id int, + response invite_resp +); +SELECT create_distributed_table('event_responses_no_pkey', 'event_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION regular_func(p invite_resp) +RETURNS int AS $$ +DECLARE + q1Result INT; + q2Result INT; + q3Result INT; +BEGIN +SELECT count(*) INTO q1Result FROM event_responses WHERE response = $1; +SELECT count(*) INTO q2Result FROM event_responses e1 LEFT JOIN event_responses e2 USING (event_id) WHERE e2.response = $1; +SELECT count(*) INTO q3Result FROM (SELECT * FROM event_responses WHERE response = $1 LIMIT 5) as foo; +RETURN q3Result+q2Result+q1Result; +END; +$$ LANGUAGE plpgsql; +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +CREATE OR REPLACE PROCEDURE regular_procedure(p invite_resp) +AS $$ +BEGIN +PERFORM * FROM event_responses WHERE response = $1 ORDER BY 1 DESC, 2 DESC, 3 DESC; +PERFORM * FROM event_responses e1 LEFT JOIN event_responses e2 USING (event_id) WHERE e2.response = $1 ORDER BY 1 DESC, 2 DESC, 3 DESC, 4 DESC; +PERFORM * FROM (SELECT * FROM event_responses WHERE response = $1 LIMIT 5) as foo ORDER BY 1 DESC, 2 DESC, 3 DESC; +END; +$$ LANGUAGE plpgsql; +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +PREPARE multi_shard_no_dist_key(invite_resp) AS select * from event_responses where response = $1::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 1; +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +PREPARE multi_shard_with_dist_key(int, invite_resp) AS select * from event_responses where event_id > $1 AND response = $2::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 1; +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +PREPARE query_pushdown_no_dist_key(invite_resp) AS select * from event_responses e1 LEFT JOIN event_responses e2 USING(event_id) where e1.response = $1::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC, 4 DESC LIMIT 1; +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +PREPARE insert_select_via_coord(invite_resp) AS INSERT INTO event_responses SELECT * FROM event_responses where response = $1::invite_resp LIMIT 1 ON CONFLICT (event_id, user_id) DO NOTHING ; +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +PREPARE insert_select_pushdown(invite_resp) AS INSERT INTO event_responses SELECT * FROM event_responses where response = $1::invite_resp ON CONFLICT (event_id, user_id) DO NOTHING; +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +PREPARE router_select_with_no_dist_key_filter(invite_resp) AS select * from event_responses where event_id = 1 AND response = $1::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 1; +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +-- rest of the tests assume the table is empty +TRUNCATE event_responses; +CREATE OR REPLACE PROCEDURE register_for_event(p_event_id int, p_user_id int, p_choice invite_resp) +LANGUAGE plpgsql AS $fn$ +BEGIN + INSERT INTO event_responses VALUES (p_event_id, p_user_id, p_choice) + ON CONFLICT (event_id, user_id) + DO UPDATE SET response = EXCLUDED.response; + + PERFORM count(*) FROM event_responses WHERE event_id = p_event_id; + + PERFORM count(*) FROM event_responses WHERE event_id = p_event_id AND false; + + UPDATE event_responses SET response = p_choice WHERE event_id = p_event_id; + +END; +$fn$; +SELECT create_distributed_function('register_for_event(int,int,invite_resp)', 'p_event_id', 'event_responses'); + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +-- call 8 times to make sure it works after the 5th time(postgres binds values after the 5th time and Citus 2nd time) +-- after 6th, the local execution caches the local plans and uses it +-- execute it both locally and remotely +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +\c - - - :worker_2_port +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +-- values 16, 17 and 19 hits the same +-- shard, so we're re-using the same cached +-- plans per statement across different distribution +-- key values +CALL register_for_event(17, 1, 'yes'); +CALL register_for_event(19, 1, 'yes'); +CALL register_for_event(17, 1, 'yes'); +CALL register_for_event(19, 1, 'yes'); +-- should work fine if the logs are enabled +\set VERBOSITY terse +SET citus.log_local_commands TO ON; +SET client_min_messages TO DEBUG2; +CALL register_for_event(19, 1, 'yes'); +DEBUG: not pushing down procedure to the same node +NOTICE: executing the command locally: INSERT INTO public.event_responses_1480001 AS citus_table_alias (event_id, user_id, response) VALUES (19, 1, 'yes'::public.invite_resp) ON CONFLICT(event_id, user_id) DO UPDATE SET response = excluded.response +NOTICE: executing the command locally: SELECT count(*) AS count FROM public.event_responses_1480001 event_responses WHERE (event_id OPERATOR(pg_catalog.=) 19) +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT NULL::integer AS event_id, NULL::integer AS user_id, NULL::public.invite_resp AS response WHERE false) event_responses(event_id, user_id, response) WHERE ((event_id OPERATOR(pg_catalog.=) 19) AND false) +NOTICE: executing the command locally: UPDATE public.event_responses_1480001 event_responses SET response = 'yes'::public.invite_resp WHERE (event_id OPERATOR(pg_catalog.=) 19) +-- should be fine even if no parameters exists in the query +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: SELECT count(*) AS count FROM public.event_responses_1480001 event_responses WHERE (event_id OPERATOR(pg_catalog.=) 16) + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: SELECT count(*) AS count FROM public.event_responses_1480001 event_responses WHERE (event_id OPERATOR(pg_catalog.=) 16) + count +--------------------------------------------------------------------- + 1 +(1 row) + +UPDATE event_responses SET response = 'no' WHERE event_id = 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: UPDATE public.event_responses_1480001 event_responses SET response = 'no'::public.invite_resp WHERE (event_id OPERATOR(pg_catalog.=) 16) +INSERT INTO event_responses VALUES (16, 666, 'maybe') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: INSERT INTO public.event_responses_1480001 AS citus_table_alias (event_id, user_id, response) VALUES (16, 666, 'maybe'::public.invite_resp) ON CONFLICT(event_id, user_id) DO UPDATE SET response = excluded.response RETURNING citus_table_alias.event_id, citus_table_alias.user_id, citus_table_alias.response + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe +(1 row) + +-- multi row INSERTs hitting the same shard +INSERT INTO event_responses VALUES (16, 666, 'maybe'), (17, 777, 'no') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan +NOTICE: executing the command locally: INSERT INTO public.event_responses_1480001 AS citus_table_alias (event_id, user_id, response) VALUES (16,666,'maybe'::public.invite_resp), (17,777,'no'::public.invite_resp) ON CONFLICT(event_id, user_id) DO UPDATE SET response = excluded.response RETURNING citus_table_alias.event_id, citus_table_alias.user_id, citus_table_alias.response + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe + 17 | 777 | no +(2 rows) + +-- now, similar tests with some settings changed +SET citus.enable_local_execution TO false; +SET citus.enable_fast_path_router_planner TO false; +CALL register_for_event(19, 1, 'yes'); +DEBUG: not pushing down procedure to the same node +-- should be fine even if no parameters exists in the query +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 + count +--------------------------------------------------------------------- + 2 +(1 row) + +UPDATE event_responses SET response = 'no' WHERE event_id = 16; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +INSERT INTO event_responses VALUES (16, 666, 'maybe') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe +(1 row) + +-- multi row INSERTs hitting the same shard +INSERT INTO event_responses VALUES (16, 666, 'maybe'), (17, 777, 'no') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe + 17 | 777 | no +(2 rows) + +-- set back to sane settings +RESET citus.enable_local_execution; +RESET citus.enable_fast_path_router_planner; +-- we'll test some 2PC states +SET citus.enable_metadata_sync TO OFF; +-- coordinated_transaction_should_use_2PC prints the internal +-- state for 2PC decision on Citus. However, even if 2PC is decided, +-- we may not necessarily use 2PC over a connection unless it does +-- a modification +CREATE OR REPLACE FUNCTION coordinated_transaction_should_use_2PC() +RETURNS BOOL LANGUAGE C STRICT VOLATILE AS 'citus', +$$coordinated_transaction_should_use_2PC$$; +-- make tests consistent +SET citus.max_adaptive_executor_pool_size TO 1; +RESET citus.enable_metadata_sync; +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +SET citus.log_remote_commands TO ON; +-- we use event_id = 2 for local execution and event_id = 1 for reemote execution +--show it here, if anything changes here, all the tests below might be broken +-- we prefer this to avoid excessive logging below +SELECT * FROM event_responses_no_pkey WHERE event_id = 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 2 +NOTICE: executing the command locally: SELECT event_id, user_id, response FROM public.event_responses_no_pkey_1480007 event_responses_no_pkey WHERE (event_id OPERATOR(pg_catalog.=) 2) + event_id | user_id | response +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM event_responses_no_pkey WHERE event_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 1 +NOTICE: issuing SELECT event_id, user_id, response FROM public.event_responses_no_pkey_1480004 event_responses_no_pkey WHERE (event_id OPERATOR(pg_catalog.=) 1) + event_id | user_id | response +--------------------------------------------------------------------- +(0 rows) + +RESET citus.log_remote_commands; +RESET citus.log_local_commands; +RESET client_min_messages; +-- single shard local command without transaction block does set the +-- internal state for 2PC, but does not require any actual entries +WITH cte_1 AS (INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *) +SELECT coordinated_transaction_should_use_2PC() FROM cte_1; + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- two local commands without transaction block set the internal 2PC state +-- but does not use remotely +WITH cte_1 AS (INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *), + cte_2 AS (INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard local modification followed by another single shard +-- local modification sets the 2PC state, but does not use remotely +BEGIN; + INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + + INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard local modification followed by a single shard +-- remote modification uses 2PC because multiple nodes involved +-- in the modification +BEGIN; + INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + + INSERT INTO event_responses_no_pkey VALUES (1, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 2 | yes +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard local modification followed by a single shard +-- remote modification uses 2PC even if it is not in an explicit +-- tx block as multiple nodes involved in the modification +WITH cte_1 AS (INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *), + cte_2 AS (INSERT INTO event_responses_no_pkey VALUES (1, 1, 'yes') RETURNING *) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard remote modification followed by a single shard +-- local modification uses 2PC as multiple nodes involved +-- in the modification +BEGIN; + INSERT INTO event_responses_no_pkey VALUES (1, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 2 | yes +(1 row) + + INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard remote modification followed by a single shard +-- local modification uses 2PC even if it is not in an explicit +-- tx block +WITH cte_1 AS (INSERT INTO event_responses_no_pkey VALUES (1, 1, 'yes') RETURNING *), + cte_2 AS (INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard local SELECT command without transaction block does not set the +-- internal state for 2PC +WITH cte_1 AS (SELECT * FROM event_responses_no_pkey WHERE event_id = 2) +SELECT coordinated_transaction_should_use_2PC() FROM cte_1; +ERROR: The transaction is not a coordinated transaction +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- two local SELECT commands without transaction block does not set the internal 2PC state +-- and does not use remotely +WITH cte_1 AS (SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2), + cte_2 AS (SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2) +SELECT count(*) FROM cte_1, cte_2; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- two local SELECT commands without transaction block does not set the internal 2PC state +-- and does not use remotely +BEGIN; + SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2; + count +--------------------------------------------------------------------- + 9 +(1 row) + + SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2; + count +--------------------------------------------------------------------- + 9 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- a local SELECT followed by a remote SELECT does not require to +-- use actual 2PC +BEGIN; + SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2; + count +--------------------------------------------------------------------- + 9 +(1 row) + + SELECT count(*) FROM event_responses_no_pkey; + count +--------------------------------------------------------------------- + 13 +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard local SELECT followed by a single shard +-- remote modification does not use 2PC, because only a single +-- machine involved in the modification +BEGIN; + SELECT * FROM event_responses_no_pkey WHERE event_id = 2; + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes + 2 | 2 | yes + 2 | 2 | yes + 2 | 2 | yes + 2 | 2 | yes + 2 | 2 | yes + 2 | 2 | yes + 2 | 2 | yes + 2 | 2 | yes +(9 rows) + + INSERT INTO event_responses_no_pkey VALUES (1, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 2 | yes +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard local SELECT followed by a single shard +-- remote modification does not use 2PC, because only a single +-- machine involved in the modification +WITH cte_1 AS (SELECT * FROM event_responses_no_pkey WHERE event_id = 2), + cte_2 AS (INSERT INTO event_responses_no_pkey VALUES (1, 1, 'yes') RETURNING *) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + f +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard remote modification followed by a single shard +-- local SELECT does not use 2PC, because only a single +-- machine involved in the modification +BEGIN; + INSERT INTO event_responses_no_pkey VALUES (1, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 2 | yes +(1 row) + + SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2; + count +--------------------------------------------------------------------- + 9 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard remote modification followed by a single shard +-- local SELECT does not use 2PC, because only a single +-- machine involved in the modification +WITH cte_1 AS (INSERT INTO event_responses_no_pkey VALUES (1, 1, 'yes') RETURNING *), + cte_2 AS (SELECT * FROM event_responses_no_pkey WHERE event_id = 2) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + f +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- multi shard local SELECT command without transaction block does not set the +-- internal state for 2PC +WITH cte_1 AS (SELECT count(*) FROM event_responses_no_pkey) +SELECT coordinated_transaction_should_use_2PC() FROM cte_1; + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- two multi-shard SELECT commands without transaction block does not set the internal 2PC state +-- and does not use remotely +WITH cte_1 AS (SELECT count(*) FROM event_responses_no_pkey), + cte_2 AS (SELECT count(*) FROM event_responses_no_pkey) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + f +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- two multi-shard SELECT commands without transaction block does not set the internal 2PC state +-- and does not use remotely +BEGIN; + SELECT count(*) FROM event_responses_no_pkey; + count +--------------------------------------------------------------------- + 17 +(1 row) + + SELECT count(*) FROM event_responses_no_pkey; + count +--------------------------------------------------------------------- + 17 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- multi-shard shard SELECT followed by a single shard +-- remote modification does not use 2PC, because only a single +-- machine involved in the modification +BEGIN; + SELECT count(*) FROM event_responses_no_pkey; + count +--------------------------------------------------------------------- + 17 +(1 row) + + INSERT INTO event_responses_no_pkey VALUES (1, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 2 | yes +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- multi shard SELECT followed by a single shard +-- remote single shard modification does not use 2PC, because only a single +-- machine involved in the modification +WITH cte_1 AS (SELECT count(*) FROM event_responses_no_pkey), + cte_2 AS (INSERT INTO event_responses_no_pkey VALUES (1, 1, 'yes') RETURNING *) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + f +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard remote modification followed by a multi shard +-- SELECT does not use 2PC, because only a single +-- machine involved in the modification +BEGIN; + INSERT INTO event_responses_no_pkey VALUES (1, 2, 'yes') RETURNING *; + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 2 | yes +(1 row) + + SELECT count(*) FROM event_responses_no_pkey; + count +--------------------------------------------------------------------- + 20 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard remote modification followed by a multi shard +-- SELECT does not use 2PC, because only a single +-- machine involved in the modification +WITH cte_1 AS (INSERT INTO event_responses_no_pkey VALUES (1, 1, 'yes') RETURNING *), + cte_2 AS (SELECT count(*) FROM event_responses_no_pkey) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + f +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- single shard local modification followed by remote multi-shard +-- modification uses 2PC as multiple nodes are involved in modifications +WITH cte_1 AS (INSERT INTO event_responses_no_pkey VALUES (2, 2, 'yes') RETURNING *), + cte_2 AS (UPDATE event_responses_no_pkey SET user_id = 1000 RETURNING *) +SELECT bool_or(coordinated_transaction_should_use_2PC()) FROM cte_1, cte_2; + bool_or +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- a local SELECT followed by a remote multi-shard UPDATE requires to +-- use actual 2PC as multiple nodes are involved in modifications +BEGIN; + SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2; + count +--------------------------------------------------------------------- + 10 +(1 row) + + UPDATE event_responses_no_pkey SET user_id = 1; +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- a local SELECT followed by a remote single-shard UPDATE does not require to +-- use actual 2PC. This is because a single node is involved in modification +BEGIN; + SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2; + count +--------------------------------------------------------------------- + 10 +(1 row) + + UPDATE event_responses_no_pkey SET user_id = 1 WHERE event_id = 1; +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +-- a remote single-shard UPDATE followed by a local single shard SELECT +-- does not require to use actual 2PC. This is because a single node +-- is involved in modification +BEGIN; + UPDATE event_responses_no_pkey SET user_id = 1 WHERE event_id = 1; + SELECT count(*) FROM event_responses_no_pkey WHERE event_id = 2; + count +--------------------------------------------------------------------- + 10 +(1 row) + +COMMIT; +SELECT count(*) FROM pg_dist_transaction; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 0 +(1 row) + +\c - - - :master_port +-- verify the local_hostname guc is used for local executions that should connect to the +-- local host +ALTER SYSTEM SET citus.local_hostname TO 'foobar'; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(0.1); -- wait to make sure the config has changed before running the GUC + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +SET citus.enable_local_execution TO false; -- force a connection to the dummy placements +-- run queries that use dummy placements for local execution +SELECT * FROM event_responses WHERE FALSE; +ERROR: connection to the remote node foobar:57636 failed with the following error: could not translate host name "foobar" to address: +WITH cte_1 AS (SELECT * FROM event_responses LIMIT 1) SELECT count(*) FROM cte_1; +ERROR: connection to the remote node foobar:57636 failed with the following error: could not translate host name "foobar" to address: +ALTER SYSTEM RESET citus.local_hostname; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(.1); -- wait to make sure the config has changed before running the GUC + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO ERROR; +SET search_path TO public; +DROP SCHEMA local_shard_execution CASCADE; diff --git a/src/test/regress/expected/local_shard_execution_replicated.out b/src/test/regress/expected/local_shard_execution_replicated.out index 200b99872..d12aa937d 100644 --- a/src/test/regress/expected/local_shard_execution_replicated.out +++ b/src/test/regress/expected/local_shard_execution_replicated.out @@ -1,3 +1,17 @@ +-- +-- LOCAL_SHARD_EXECUTION_REPLICATED +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA local_shard_execution_replicated; SET search_path TO local_shard_execution_replicated; SET citus.shard_count TO 4; @@ -225,7 +239,7 @@ RETURNING *; INSERT INTO distributed_table SELECT * FROM distributed_table WHERE key = 1 OFFSET 0 ON CONFLICT DO NOTHING; NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) OFFSET 0 NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) SELECT key, value, age FROM read_intermediate_result('insert_select_XXX_1500001'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint) ON CONFLICT DO NOTHING +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('insert_select_XXX_1500001'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint) ON CONFLICT DO NOTHING INSERT INTO distributed_table SELECT 1, '1',15 FROM distributed_table WHERE key = 2 LIMIT 1 ON CONFLICT DO NOTHING; NOTICE: executing the command locally: SELECT 1 AS key, '1'::text AS value, int8(15) AS age FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (key OPERATOR(pg_catalog.=) 2) LIMIT 1 -- sanity check: multi-shard INSERT..SELECT pushdown goes through distributed execution @@ -764,8 +778,8 @@ NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_r NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1500002_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1500002_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1500003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1500003_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1500004_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1500004_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 -NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key) SELECT key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1500003_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) -NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 AS citus_table_alias (key) SELECT key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1500004_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key) SELECT intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1500003_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 AS citus_table_alias (key) SELECT intermediate_result.key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1500004_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) SELECT count(*) FROM distributed_table WHERE key = -6; NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) '-6'::integer) count diff --git a/src/test/regress/expected/local_shard_execution_replicated_0.out b/src/test/regress/expected/local_shard_execution_replicated_0.out new file mode 100644 index 000000000..7a0a77ece --- /dev/null +++ b/src/test/regress/expected/local_shard_execution_replicated_0.out @@ -0,0 +1,2461 @@ +-- +-- LOCAL_SHARD_EXECUTION_REPLICATED +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA local_shard_execution_replicated; +SET search_path TO local_shard_execution_replicated; +SET citus.shard_count TO 4; +SET citus.shard_replication_factor TO 2; +SET citus.next_shard_id TO 1500000; +CREATE TABLE reference_table (key int PRIMARY KEY); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE distributed_table (key int PRIMARY KEY , value text, age bigint CHECK (age > 10)); +SELECT create_distributed_table('distributed_table','key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE second_distributed_table (key int PRIMARY KEY , value text); +SELECT create_distributed_table('second_distributed_table','key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- ingest some data to enable some tests with data +INSERT INTO distributed_table VALUES (1, '1', 20); +-- This GUC prevents to acquire the remote lock for replicated +-- tables +BEGIN; + SET LOCAL citus.allow_modifications_from_workers_to_replicated_tables TO false; + INSERT INTO second_distributed_table VALUES (1, '1'); + INSERT INTO reference_table VALUES (1); +COMMIT; +-- a simple test for +CREATE TABLE collections_list ( + key bigserial, + ser bigserial, + ts timestamptz, + collection_id integer, + value numeric, + PRIMARY KEY(key, collection_id) +) PARTITION BY LIST (collection_id ); +SELECT create_distributed_table('collections_list', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE collections_list_0 + PARTITION OF collections_list (key, ser, ts, collection_id, value) + FOR VALUES IN ( 0 ); +-- create a volatile function that returns the local node id +CREATE OR REPLACE FUNCTION get_local_node_id_volatile() +RETURNS INT AS $$ +DECLARE localGroupId int; +BEGIN + SELECT groupid INTO localGroupId FROM pg_dist_local_group; + RETURN localGroupId; +END; $$ language plpgsql VOLATILE; +SELECT create_distributed_function('get_local_node_id_volatile()'); +NOTICE: procedure local_shard_execution_replicated.get_local_node_id_volatile is already distributed +DETAIL: Citus distributes procedures with CREATE [PROCEDURE|FUNCTION|AGGREGATE] commands + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +-- test case for issue #3556 +CREATE TABLE accounts (id text PRIMARY KEY); +CREATE TABLE stats (account_id text PRIMARY KEY, spent int); +SELECT create_distributed_table('accounts', 'id', colocate_with => 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('stats', 'account_id', colocate_with => 'accounts'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO accounts (id) VALUES ('foo'); +INSERT INTO stats (account_id, spent) VALUES ('foo', 100); +CREATE TABLE abcd(a int, b int, c int, d int); +SELECT create_distributed_table('abcd', 'b'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO abcd VALUES (1,2,3,4); +INSERT INTO abcd VALUES (2,3,4,5); +INSERT INTO abcd VALUES (3,4,5,6); +ALTER TABLE abcd DROP COLUMN a; +-- connection worker and get ready for the tests +\c - - - :worker_1_port +SET search_path TO local_shard_execution_replicated; +-- test case for issue #3556 +SET citus.log_intermediate_results TO TRUE; +SET client_min_messages TO DEBUG1; +SELECT * +FROM +( + WITH accounts_cte AS ( + SELECT id AS account_id + FROM accounts + ), + joined_stats_cte_1 AS ( + SELECT spent, account_id + FROM stats + INNER JOIN accounts_cte USING (account_id) + ), + joined_stats_cte_2 AS ( + SELECT spent, account_id + FROM joined_stats_cte_1 + INNER JOIN accounts_cte USING (account_id) + ) + SELECT SUM(spent) OVER (PARTITION BY coalesce(account_id, NULL)) + FROM accounts_cte + INNER JOIN joined_stats_cte_2 USING (account_id) +) inner_query; +DEBUG: CTE joined_stats_cte_1 is going to be inlined via distributed planning +DEBUG: CTE joined_stats_cte_2 is going to be inlined via distributed planning +DEBUG: generating subplan XXX_1 for CTE accounts_cte: SELECT id AS account_id FROM local_shard_execution_replicated.accounts +DEBUG: generating subplan XXX_2 for subquery SELECT sum(joined_stats_cte_2.spent) OVER (PARTITION BY COALESCE(accounts_cte.account_id, NULL::text)) AS sum FROM ((SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte JOIN (SELECT joined_stats_cte_1.spent, joined_stats_cte_1.account_id FROM ((SELECT stats.spent, stats.account_id FROM (local_shard_execution_replicated.stats JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte_2 USING (account_id))) joined_stats_cte_1 JOIN (SELECT intermediate_result.account_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(account_id text)) accounts_cte_1 USING (account_id))) joined_stats_cte_2 USING (account_id)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum FROM (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(sum bigint)) inner_query +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file + sum +--------------------------------------------------------------------- + 100 +(1 row) + +SET citus.log_intermediate_results TO DEFAULT; +SET client_min_messages TO DEFAULT; +--- enable logging to see which tasks are executed locally +SET citus.log_local_commands TO ON; +-- first, make sure that local execution works fine +-- with simple queries that are not in transcation blocks +SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- multiple tasks both of which are local should NOT use local execution +-- because local execution means executing the tasks locally, so the executor +-- favors parallel execution even if everyting is local to node +SELECT count(*) FROM distributed_table WHERE key IN (1,6); + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- queries that hit any remote shards should NOT use local execution +SELECT count(*) FROM distributed_table WHERE key IN (1,11); + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM distributed_table; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- modifications also follow the same rules +INSERT INTO reference_table VALUES (1) ON CONFLICT DO NOTHING; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 AS citus_table_alias (key) VALUES (1) ON CONFLICT DO NOTHING +INSERT INTO distributed_table VALUES (1, '1', 21) ON CONFLICT DO NOTHING; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '1'::text, 21) ON CONFLICT DO NOTHING +-- local query +DELETE FROM distributed_table WHERE key = 1 AND age = 21; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((key OPERATOR(pg_catalog.=) 1) AND (age OPERATOR(pg_catalog.=) 21)) +-- hitting multiple shards, so should be a distributed execution +DELETE FROM distributed_table WHERE age = 21; +-- although both shards are local, the executor choose the parallel execution +-- over the wire because as noted above local execution is sequential +DELETE FROM second_distributed_table WHERE key IN (1,6); +-- similarly, any multi-shard query just follows distributed execution +DELETE FROM second_distributed_table; +-- load some more data for the following tests +INSERT INTO second_distributed_table VALUES (1, '1'); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.second_distributed_table_1500005 (key, value) VALUES (1, '1'::text) +-- INSERT .. SELECT hitting a single single (co-located) shard(s) should +-- be executed locally +INSERT INTO distributed_table +SELECT + distributed_table.* +FROM + distributed_table, second_distributed_table +WHERE + distributed_table.key = 1 and distributed_table.key=second_distributed_table.key +ON CONFLICT(key) DO UPDATE SET value = '22' +RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table, local_shard_execution_replicated.second_distributed_table_1500005 second_distributed_table WHERE (((distributed_table.key OPERATOR(pg_catalog.=) 1) AND (distributed_table.key OPERATOR(pg_catalog.=) second_distributed_table.key)) AND (distributed_table.key IS NOT NULL)) ON CONFLICT(key) DO UPDATE SET value = '22'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 22 | 20 +(1 row) + +-- INSERT .. SELECT hitting multi-shards should go thourgh distributed execution +INSERT INTO distributed_table +SELECT + distributed_table.* +FROM + distributed_table, second_distributed_table +WHERE + distributed_table.key != 1 and distributed_table.key=second_distributed_table.key +ON CONFLICT(key) DO UPDATE SET value = '22' +RETURNING *; + key | value | age +--------------------------------------------------------------------- +(0 rows) + +-- INSERT..SELECT via coordinator consists of two steps, select + COPY +-- that's why it is disallowed to use local execution even if the SELECT +-- can be executed locally +INSERT INTO distributed_table SELECT * FROM distributed_table WHERE key = 1 OFFSET 0 ON CONFLICT DO NOTHING; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) OFFSET 0 +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) SELECT key, value, age FROM read_intermediate_result('insert_select_XXX_1500001'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint) ON CONFLICT DO NOTHING +INSERT INTO distributed_table SELECT 1, '1',15 FROM distributed_table WHERE key = 2 LIMIT 1 ON CONFLICT DO NOTHING; +NOTICE: executing the command locally: SELECT 1 AS key, '1'::text AS value, int8(15) AS age FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (key OPERATOR(pg_catalog.=) 2) LIMIT 1 +-- sanity check: multi-shard INSERT..SELECT pushdown goes through distributed execution +INSERT INTO distributed_table SELECT * FROM distributed_table ON CONFLICT DO NOTHING; +-- Ensure tuple data in explain analyze output is the same on all PG versions +SET citus.enable_binary_protocol = TRUE; +-- EXPLAIN for local execution just works fine +-- though going through distributed execution +EXPLAIN (COSTS OFF) SELECT * FROM distributed_table WHERE key = 1 AND age = 20; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Index Scan using distributed_table_pkey_1500001 on distributed_table_1500001 distributed_table + Index Cond: (key = 1) + Filter: (age = 20) +(8 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM distributed_table WHERE key = 1 AND age = 20; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) (actual rows=1 loops=1) + Task Count: 1 + Tuple data received from nodes: 14 bytes + Tasks Shown: All + -> Task + Tuple data received from node: 14 bytes + Node: host=localhost port=xxxxx dbname=regression + -> Index Scan using distributed_table_pkey_1500001 on distributed_table_1500001 distributed_table (actual rows=1 loops=1) + Index Cond: (key = 1) + Filter: (age = 20) +(10 rows) + +EXPLAIN (ANALYZE ON, COSTS OFF, SUMMARY OFF, TIMING OFF) +WITH r AS ( SELECT GREATEST(random(), 2) z,* FROM distributed_table) +SELECT 1 FROM r WHERE z < 3; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) (actual rows=1 loops=1) + -> Distributed Subplan XXX_1 + Intermediate Data Size: 40 bytes + Result destination: Write locally + -> Custom Scan (Citus Adaptive) (actual rows=1 loops=1) + Task Count: 4 + Tuple data received from nodes: 22 bytes + Tasks Shown: One of 4 + -> Task + Tuple data received from node: 22 bytes + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on distributed_table_1500001 distributed_table (actual rows=1 loops=1) + Task Count: 1 + Tuple data received from nodes: 4 bytes + Tasks Shown: All + -> Task + Tuple data received from node: 4 bytes + Node: host=localhost port=xxxxx dbname=regression + -> Function Scan on read_intermediate_result intermediate_result (actual rows=1 loops=1) + Filter: (z < '3'::double precision) +(20 rows) + +EXPLAIN (COSTS OFF) DELETE FROM distributed_table WHERE key = 1 AND age = 20; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) + Task Count: 1 + Tasks Shown: All + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> Delete on distributed_table_1500001 distributed_table + -> Index Scan using distributed_table_pkey_1500001 on distributed_table_1500001 distributed_table + Index Cond: (key = 1) + Filter: (age = 20) +(9 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) DELETE FROM distributed_table WHERE key = 1 AND age = 20; + 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 + -> Delete on distributed_table_1500001 distributed_table (actual rows=0 loops=1) + -> Index Scan using distributed_table_pkey_1500001 on distributed_table_1500001 distributed_table (actual rows=1 loops=1) + Index Cond: (key = 1) + Filter: (age = 20) +(9 rows) + +-- show that EXPLAIN ANALYZE deleted the row +SELECT * FROM distributed_table WHERE key = 1 AND age = 20 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE ((key OPERATOR(pg_catalog.=) 1) AND (age OPERATOR(pg_catalog.=) 20)) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM second_distributed_table WHERE key = 1 ORDER BY 1,2; +NOTICE: executing the command locally: SELECT key, value FROM local_shard_execution_replicated.second_distributed_table_1500005 second_distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value + key | value +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +-- Put row back for other tests +INSERT INTO distributed_table VALUES (1, '22', 20); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 (key, value, age) VALUES (1, '22'::text, 20) +SET citus.enable_ddl_propagation TO OFF; +CREATE VIEW abcd_view AS SELECT * FROM abcd; +RESET citus.enable_ddl_propagation; +SELECT * FROM abcd first join abcd second on first.b = second.b ORDER BY 1,2,3,4; + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +BEGIN; +SELECT * FROM abcd first join abcd second on first.b = second.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.b, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500025 first JOIN local_shard_execution_replicated.abcd_1500025 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.b, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500026 first JOIN local_shard_execution_replicated.abcd_1500026 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.b, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500027 first JOIN local_shard_execution_replicated.abcd_1500027 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.b, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500028 first JOIN local_shard_execution_replicated.abcd_1500028 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd_view first join abcd_view second on first.b = second.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT abcd.b, abcd.c, abcd.d, abcd_1.b, abcd_1.c, abcd_1.d FROM (local_shard_execution_replicated.abcd_1500025 abcd JOIN local_shard_execution_replicated.abcd_1500025 abcd_1 ON ((abcd.b OPERATOR(pg_catalog.=) abcd_1.b))) WHERE true +NOTICE: executing the command locally: SELECT abcd.b, abcd.c, abcd.d, abcd_1.b, abcd_1.c, abcd_1.d FROM (local_shard_execution_replicated.abcd_1500026 abcd JOIN local_shard_execution_replicated.abcd_1500026 abcd_1 ON ((abcd.b OPERATOR(pg_catalog.=) abcd_1.b))) WHERE true +NOTICE: executing the command locally: SELECT abcd.b, abcd.c, abcd.d, abcd_1.b, abcd_1.c, abcd_1.d FROM (local_shard_execution_replicated.abcd_1500027 abcd JOIN local_shard_execution_replicated.abcd_1500027 abcd_1 ON ((abcd.b OPERATOR(pg_catalog.=) abcd_1.b))) WHERE true +NOTICE: executing the command locally: SELECT abcd.b, abcd.c, abcd.d, abcd_1.b, abcd_1.c, abcd_1.d FROM (local_shard_execution_replicated.abcd_1500028 abcd JOIN local_shard_execution_replicated.abcd_1500028 abcd_1 ON ((abcd.b OPERATOR(pg_catalog.=) abcd_1.b))) WHERE true + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd first full join abcd second on first.b = second.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT worker_column_1 AS b, worker_column_2 AS c, worker_column_3 AS d, worker_column_4 AS b, worker_column_5 AS c, worker_column_6 AS d FROM (SELECT first.b AS worker_column_1, first.c AS worker_column_2, first.d AS worker_column_3, second.b AS worker_column_4, second.c AS worker_column_5, second.d AS worker_column_6 FROM (local_shard_execution_replicated.abcd_1500025 first FULL JOIN local_shard_execution_replicated.abcd_1500025 second ON ((first.b OPERATOR(pg_catalog.=) second.b)))) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS b, worker_column_2 AS c, worker_column_3 AS d, worker_column_4 AS b, worker_column_5 AS c, worker_column_6 AS d FROM (SELECT first.b AS worker_column_1, first.c AS worker_column_2, first.d AS worker_column_3, second.b AS worker_column_4, second.c AS worker_column_5, second.d AS worker_column_6 FROM (local_shard_execution_replicated.abcd_1500026 first FULL JOIN local_shard_execution_replicated.abcd_1500026 second ON ((first.b OPERATOR(pg_catalog.=) second.b)))) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS b, worker_column_2 AS c, worker_column_3 AS d, worker_column_4 AS b, worker_column_5 AS c, worker_column_6 AS d FROM (SELECT first.b AS worker_column_1, first.c AS worker_column_2, first.d AS worker_column_3, second.b AS worker_column_4, second.c AS worker_column_5, second.d AS worker_column_6 FROM (local_shard_execution_replicated.abcd_1500027 first FULL JOIN local_shard_execution_replicated.abcd_1500027 second ON ((first.b OPERATOR(pg_catalog.=) second.b)))) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS b, worker_column_2 AS c, worker_column_3 AS d, worker_column_4 AS b, worker_column_5 AS c, worker_column_6 AS d FROM (SELECT first.b AS worker_column_1, first.c AS worker_column_2, first.d AS worker_column_3, second.b AS worker_column_4, second.c AS worker_column_5, second.d AS worker_column_6 FROM (local_shard_execution_replicated.abcd_1500028 first FULL JOIN local_shard_execution_replicated.abcd_1500028 second ON ((first.b OPERATOR(pg_catalog.=) second.b)))) worker_subquery + b | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd first join abcd second USING(b) ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500025 first JOIN local_shard_execution_replicated.abcd_1500025 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500026 first JOIN local_shard_execution_replicated.abcd_1500026 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500027 first JOIN local_shard_execution_replicated.abcd_1500027 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d FROM (local_shard_execution_replicated.abcd_1500028 first JOIN local_shard_execution_replicated.abcd_1500028 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) WHERE true + b | c | d | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 3 | 4 + 3 | 4 | 5 | 4 | 5 + 4 | 5 | 6 | 5 | 6 +(3 rows) + +END; +BEGIN; +SELECT * FROM abcd first join abcd second USING(b) join abcd third on first.b=third.b ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d, third.b, third.c, third.d FROM ((local_shard_execution_replicated.abcd_1500025 first JOIN local_shard_execution_replicated.abcd_1500025 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) JOIN local_shard_execution_replicated.abcd_1500025 third ON ((first.b OPERATOR(pg_catalog.=) third.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d, third.b, third.c, third.d FROM ((local_shard_execution_replicated.abcd_1500026 first JOIN local_shard_execution_replicated.abcd_1500026 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) JOIN local_shard_execution_replicated.abcd_1500026 third ON ((first.b OPERATOR(pg_catalog.=) third.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d, third.b, third.c, third.d FROM ((local_shard_execution_replicated.abcd_1500027 first JOIN local_shard_execution_replicated.abcd_1500027 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) JOIN local_shard_execution_replicated.abcd_1500027 third ON ((first.b OPERATOR(pg_catalog.=) third.b))) WHERE true +NOTICE: executing the command locally: SELECT first.b, first.c, first.d, second.c, second.d, third.b, third.c, third.d FROM ((local_shard_execution_replicated.abcd_1500028 first JOIN local_shard_execution_replicated.abcd_1500028 second ON ((first.b OPERATOR(pg_catalog.=) second.b))) JOIN local_shard_execution_replicated.abcd_1500028 third ON ((first.b OPERATOR(pg_catalog.=) third.b))) WHERE true + b | c | d | c | d | b | c | d +--------------------------------------------------------------------- + 2 | 3 | 4 | 3 | 4 | 2 | 3 | 4 + 3 | 4 | 5 | 4 | 5 | 3 | 4 | 5 + 4 | 5 | 6 | 5 | 6 | 4 | 5 | 6 +(3 rows) + +END; +-- copy always happens via distributed execution irrespective of the +-- shards that are accessed +COPY reference_table FROM STDIN; +COPY distributed_table FROM STDIN WITH CSV; +COPY second_distributed_table FROM STDIN WITH CSV; +-- the behaviour in transaction blocks is the following: + -- (a) Unless the first query is a local query, always use distributed execution. + -- (b) If the executor has used local execution, it has to use local execution + -- for the remaining of the transaction block. If that's not possible, the + -- executor has to error out +-- rollback should be able to rollback local execution +BEGIN; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 29 | 20 +(1 row) + + SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 29 | 20 +(1 row) + +ROLLBACK; +-- make sure that the value is rollbacked +SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 22 | 20 +(1 row) + +-- rollback should be able to rollback both the local and distributed executions +BEGIN; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 29 | 20 +(1 row) + + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table + SELECT count(*) FROM second_distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500005 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500006 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500007 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500008 second_distributed_table WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + +ROLLBACK; +-- make sure that everything is rollbacked +SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 22 | 20 +(1 row) + +SELECT count(*) FROM second_distributed_table; + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT * FROM second_distributed_table; + key | value +--------------------------------------------------------------------- + 1 | 1 + 6 | '6' +(2 rows) + +-- very simple examples, an SELECTs should see the modifications +-- that has done before +BEGIN; + -- INSERT is executed locally + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '23' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '23'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 23 | 20 +(1 row) + + -- since the INSERT is executed locally, the SELECT should also be + -- executed locally and see the changes + SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- + 1 | 23 | 20 +(1 row) + + -- multi-shard SELECTs are now forced to use local execution on + -- the shards that reside on this node + SELECT * FROM distributed_table WHERE value = '23' ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) + key | value | age +--------------------------------------------------------------------- + 1 | 23 | 20 +(1 row) + + -- similarly, multi-shard modifications should use local exection + -- on the shards that reside on this node + DELETE FROM distributed_table WHERE value = '23'; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) + -- make sure that the value is deleted + SELECT * FROM distributed_table WHERE value = '23' ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (value OPERATOR(pg_catalog.=) '23'::text) + key | value | age +--------------------------------------------------------------------- +(0 rows) + +COMMIT; +-- make sure that we've committed everything +SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- +(0 rows) + +-- if we start with a distributed execution, we should keep +-- using that and never switch back to local execution +BEGIN; + DELETE FROM distributed_table WHERE value = '11'; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (value OPERATOR(pg_catalog.=) '11'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (value OPERATOR(pg_catalog.=) '11'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (value OPERATOR(pg_catalog.=) '11'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (value OPERATOR(pg_catalog.=) '11'::text) + -- although this command could have been executed + -- locally, it is not going to be executed locally + SELECT * FROM distributed_table WHERE key = 1 ORDER BY 1,2,3; +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) ORDER BY key, value, age + key | value | age +--------------------------------------------------------------------- +(0 rows) + + -- but we can still execute parallel queries, even if + -- they are utility commands + TRUNCATE distributed_table CASCADE; +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE + -- TRUNCATE didn't cascade into second_distributed_table + SELECT count(*) FROM second_distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500005 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500006 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500007 second_distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500008 second_distributed_table WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + +ROLLBACK; +-- load some data +INSERT INTO reference_table SELECT i FROM generate_series(500, 600) i; +NOTICE: executing the copy locally for shard xxxxx +-- show that complex tx blocks work fine +BEGIN; + INSERT INTO reference_table VALUES (701); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 (key) VALUES (701) + INSERT INTO distributed_table VALUES (701, '701', 701); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 (key, value, age) VALUES (701, '701'::text, 701) + INSERT INTO second_distributed_table VALUES (701, '701'); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.second_distributed_table_1500005 (key, value) VALUES (701, '701'::text) + DELETE FROM reference_table WHERE key = 701; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.reference_table_1500000 reference_table WHERE (key OPERATOR(pg_catalog.=) 701) + SELECT count(*) FROM distributed_table WHERE key = 701; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 701) + count +--------------------------------------------------------------------- + 1 +(1 row) + + SELECT count(*) FROM second_distributed_table WHERE key = 701; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.second_distributed_table_1500005 second_distributed_table WHERE (key OPERATOR(pg_catalog.=) 701) + count +--------------------------------------------------------------------- + 1 +(1 row) + + -- multi-shard commands should also see the changes + SELECT count(*) FROM distributed_table WHERE key > 700; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.>) 700) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (key OPERATOR(pg_catalog.>) 700) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.>) 700) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (key OPERATOR(pg_catalog.>) 700) + count +--------------------------------------------------------------------- + 1 +(1 row) + + -- we can still do multi-shard commands + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table +ROLLBACK; +-- multiple queries hitting different shards can be executed locally +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + + SELECT count(*) FROM distributed_table WHERE key = 6; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 1 +(1 row) + + SELECT count(*) FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + count +--------------------------------------------------------------------- + 0 +(1 row) + +ROLLBACK; +-- a local query followed by TRUNCATE command can be executed locally +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + + TRUNCATE distributed_table CASCADE; +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +ROLLBACK; +-- a local query is followed by an INSERT..SELECT via the coordinator +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + + INSERT INTO distributed_table (key) SELECT i FROM generate_series(1,1) i; +NOTICE: executing the copy locally for shard xxxxx +ROLLBACK; +BEGIN; +SET citus.enable_repartition_joins TO ON; +SET citus.enable_unique_job_ids TO off; +SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM distributed_table d1 join distributed_table d2 using(age); +NOTICE: executing the command locally: SELECT partition_index, 'repartition_64_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_64_1','SELECT age AS column1 FROM local_shard_execution_replicated.distributed_table_1500001 d1 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_64_3' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_64_3','SELECT age AS column1 FROM local_shard_execution_replicated.distributed_table_1500003 d1 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_65_1' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_65_1','SELECT age AS column1 FROM local_shard_execution_replicated.distributed_table_1500001 d2 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartition_65_3' || '_' || partition_index::text , rows_written FROM pg_catalog.worker_partition_query_result('repartition_65_3','SELECT age AS column1 FROM local_shard_execution_replicated.distributed_table_1500003 d2 WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true,true,true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_1_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_2_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_3_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_4_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_0']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_1_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_2_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_3_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_4_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_1']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_1']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_1_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_2_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_3_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_4_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_2']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_2']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_1_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_2_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_3_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_64_4_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_1_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_2_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_3_3']::text[],'localhost',57637) bytes +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartition_65_4_3']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_64_1_0,repartition_64_2_0,repartition_64_3_0,repartition_64_4_0}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_65_1_0,repartition_65_2_0,repartition_65_3_0,repartition_65_4_0}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_64_1_1,repartition_64_2_1,repartition_64_3_1,repartition_64_4_1}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_65_1_1,repartition_65_2_1,repartition_65_3_1,repartition_65_4_1}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_64_1_2,repartition_64_2_2,repartition_64_3_2,repartition_64_4_2}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_65_1_2,repartition_65_2_2,repartition_65_3_2,repartition_65_4_2}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM (read_intermediate_results('{repartition_64_1_3,repartition_64_2_3,repartition_64_3_3,repartition_64_4_3}'::text[], 'binary'::citus_copy_format) intermediate_result(column1 bigint) JOIN read_intermediate_results('{repartition_65_1_3,repartition_65_2_3,repartition_65_3_3,repartition_65_4_3}'::text[], 'binary'::citus_copy_format) intermediate_result_1(column1 bigint) ON ((intermediate_result.column1 OPERATOR(pg_catalog.=) intermediate_result_1.column1))) WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + +ROLLBACK; +-- a local query is followed by an INSERT..SELECT with re-partitioning +BEGIN; + SELECT count(*) FROM distributed_table WHERE key = 6; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 1 +(1 row) + + INSERT INTO reference_table (key) SELECT -key FROM distributed_table; +NOTICE: executing the command locally: SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true +NOTICE: executing the copy locally for shard xxxxx + INSERT INTO distributed_table (key) SELECT -key FROM distributed_table; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1500001_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1500001_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1500002_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1500002_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1500003_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1500003_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_1500004_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_1500004_to','SELECT (OPERATOR(pg_catalog.-) key) AS key FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key) SELECT key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1500003_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 AS citus_table_alias (key) SELECT key FROM read_intermediate_results('{repartitioned_results_xxxxx_from_1500004_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(key integer) + SELECT count(*) FROM distributed_table WHERE key = -6; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) '-6'::integer) + count +--------------------------------------------------------------------- + 1 +(1 row) + +ROLLBACK; +INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 11 | 21 +(1 row) + +BEGIN; + DELETE FROM distributed_table WHERE key = 1; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + EXPLAIN ANALYZE DELETE FROM distributed_table WHERE key = 1; +ERROR: cannot execute command because a local execution has accessed a placement in the transaction +DETAIL: Some parallel commands cannot be executed if a previous command has already been executed locally +HINT: Try re-running the transaction with "SET LOCAL citus.enable_local_execution TO OFF;" +ROLLBACK; +BEGIN; + INSERT INTO distributed_table VALUES (11, '111',29) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 AS citus_table_alias (key, value, age) VALUES (11, '111'::text, 29) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 11 | 29 | 121 +(1 row) + + -- this is already disallowed on the nodes, adding it in case we + -- support DDLs from the worker nodes in the future + ALTER TABLE distributed_table ADD COLUMN x INT; +ERROR: operation is not allowed on this node +HINT: Connect to the coordinator and run it again. +ROLLBACK; +BEGIN; + INSERT INTO distributed_table VALUES (11, '111',29) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 AS citus_table_alias (key, value, age) VALUES (11, '111'::text, 29) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 11 | 29 | 121 +(1 row) + + -- this is already disallowed because VACUUM cannot be executed in tx block + -- adding in case this is supported some day + VACUUM second_distributed_table; +ERROR: VACUUM cannot run inside a transaction block +ROLLBACK; +-- make sure that functions can use local execution +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE PROCEDURE only_local_execution() AS $$ + DECLARE cnt INT; + BEGIN + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; + SELECT count(*) INTO cnt FROM distributed_table WHERE key = 1; + DELETE FROM distributed_table WHERE key = 1; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution(); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text +CONTEXT: SQL statement "INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'" +PL/pgSQL function only_local_execution() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table WHERE key = 1" +PL/pgSQL function only_local_execution() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "DELETE FROM distributed_table WHERE key = 1" +PL/pgSQL function only_local_execution() line XX at SQL statement +-- insert a row that we need in the next tests +INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text +-- make sure that functions can use local execution +CREATE OR REPLACE PROCEDURE only_local_execution_with_function_evaluation() AS $$ + DECLARE nodeId INT; + BEGIN + -- fast path router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table WHERE key = 1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + + -- regular router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = 1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution_with_function_evaluation(); +NOTICE: executing the command locally: SELECT local_shard_execution_replicated.get_local_node_id_volatile() AS get_local_node_id_volatile FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table WHERE key = 1" +PL/pgSQL function only_local_execution_with_function_evaluation() line XX at SQL statement +NOTICE: executing the command locally: SELECT local_shard_execution_replicated.get_local_node_id_volatile() AS get_local_node_id_volatile FROM (local_shard_execution_replicated.distributed_table_1500001 d1(key, value, age) JOIN local_shard_execution_replicated.distributed_table_1500001 d2(key, value, age) USING (key)) WHERE (d1.key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = 1" +PL/pgSQL function only_local_execution_with_function_evaluation() line XX at SQL statement +CREATE OR REPLACE PROCEDURE only_local_execution_with_params(int) AS $$ + DECLARE cnt INT; + BEGIN + INSERT INTO distributed_table VALUES ($1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; + SELECT count(*) INTO cnt FROM distributed_table WHERE key = $1; + DELETE FROM distributed_table WHERE key = $1; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution_with_params(1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '29'::text +CONTEXT: SQL statement "INSERT INTO distributed_table VALUES ($1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'" +PL/pgSQL function only_local_execution_with_params(integer) line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table WHERE key = $1" +PL/pgSQL function only_local_execution_with_params(integer) line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "DELETE FROM distributed_table WHERE key = $1" +PL/pgSQL function only_local_execution_with_params(integer) line XX at SQL statement +CREATE OR REPLACE PROCEDURE only_local_execution_with_function_evaluation_param(int) AS $$ + DECLARE nodeId INT; + BEGIN + -- fast path router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table WHERE key = $1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + + -- regular router + SELECT get_local_node_id_volatile() INTO nodeId FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = $1; + IF nodeId <= 0 THEN + RAISE NOTICE 'unexpected node id'; + END IF; + END; +$$ LANGUAGE plpgsql; +CALL only_local_execution_with_function_evaluation_param(1); +NOTICE: executing the command locally: SELECT local_shard_execution_replicated.get_local_node_id_volatile() AS get_local_node_id_volatile FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table WHERE key = $1" +PL/pgSQL function only_local_execution_with_function_evaluation_param(integer) line XX at SQL statement +NOTICE: executing the command locally: SELECT local_shard_execution_replicated.get_local_node_id_volatile() AS get_local_node_id_volatile FROM (local_shard_execution_replicated.distributed_table_1500001 d1(key, value, age) JOIN local_shard_execution_replicated.distributed_table_1500001 d2(key, value, age) USING (key)) WHERE (d1.key OPERATOR(pg_catalog.=) $1) +CONTEXT: SQL statement "SELECT get_local_node_id_volatile() FROM distributed_table d1 JOIN distributed_table d2 USING (key) WHERE d1.key = $1" +PL/pgSQL function only_local_execution_with_function_evaluation_param(integer) line XX at SQL statement +CREATE OR REPLACE PROCEDURE local_execution_followed_by_dist() AS $$ + DECLARE cnt INT; + BEGIN + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'; + SELECT count(*) INTO cnt FROM distributed_table WHERE key = 1; + DELETE FROM distributed_table; + SELECT count(*) INTO cnt FROM distributed_table; + END; +$$ LANGUAGE plpgsql; +RESET citus.enable_metadata_sync; +CALL local_execution_followed_by_dist(); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text +CONTEXT: SQL statement "INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29'" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table WHERE key = 1" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table +CONTEXT: SQL statement "DELETE FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table +CONTEXT: SQL statement "DELETE FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table +CONTEXT: SQL statement "DELETE FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table +CONTEXT: SQL statement "DELETE FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true +CONTEXT: SQL statement "SELECT count(*) FROM distributed_table" +PL/pgSQL function local_execution_followed_by_dist() line XX at SQL statement +-- test CTEs, including modifying CTEs +WITH local_insert AS (INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *), +distributed_local_mixed AS (SELECT * FROM reference_table WHERE key IN (SELECT key FROM local_insert)) +SELECT * FROM local_insert, distributed_local_mixed; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age +NOTICE: executing the command locally: SELECT key FROM local_shard_execution_replicated.reference_table_1500000 reference_table WHERE (key OPERATOR(pg_catalog.=) ANY (SELECT local_insert.key FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert)) +NOTICE: executing the command locally: SELECT local_insert.key, local_insert.value, local_insert.age, distributed_local_mixed.key FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT intermediate_result.key FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) distributed_local_mixed + key | value | age | key +--------------------------------------------------------------------- + 1 | 11 | 21 | 1 +(1 row) + +-- since we start with parallel execution, we do not switch back to local execution in the +-- latter CTEs +WITH distributed_local_mixed AS (SELECT * FROM distributed_table), +local_insert AS (INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '29' RETURNING *) +SELECT * FROM local_insert, distributed_local_mixed ORDER BY 1,2,3,4,5; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '29'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age +NOTICE: executing the command locally: SELECT worker_column_1 AS key, worker_column_2 AS value, worker_column_3 AS age, worker_column_4 AS key, worker_column_5 AS value, worker_column_6 AS age FROM (SELECT local_insert.key AS worker_column_1, local_insert.value AS worker_column_2, local_insert.age AS worker_column_3, distributed_local_mixed.key AS worker_column_4, distributed_local_mixed.value AS worker_column_5, distributed_local_mixed.age AS worker_column_6 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table) distributed_local_mixed) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS key, worker_column_2 AS value, worker_column_3 AS age, worker_column_4 AS key, worker_column_5 AS value, worker_column_6 AS age FROM (SELECT local_insert.key AS worker_column_1, local_insert.value AS worker_column_2, local_insert.age AS worker_column_3, distributed_local_mixed.key AS worker_column_4, distributed_local_mixed.value AS worker_column_5, distributed_local_mixed.age AS worker_column_6 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table) distributed_local_mixed) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS key, worker_column_2 AS value, worker_column_3 AS age, worker_column_4 AS key, worker_column_5 AS value, worker_column_6 AS age FROM (SELECT local_insert.key AS worker_column_1, local_insert.value AS worker_column_2, local_insert.age AS worker_column_3, distributed_local_mixed.key AS worker_column_4, distributed_local_mixed.value AS worker_column_5, distributed_local_mixed.age AS worker_column_6 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table) distributed_local_mixed) worker_subquery +NOTICE: executing the command locally: SELECT worker_column_1 AS key, worker_column_2 AS value, worker_column_3 AS age, worker_column_4 AS key, worker_column_5 AS value, worker_column_6 AS age FROM (SELECT local_insert.key AS worker_column_1, local_insert.value AS worker_column_2, local_insert.age AS worker_column_3, distributed_local_mixed.key AS worker_column_4, distributed_local_mixed.value AS worker_column_5, distributed_local_mixed.age AS worker_column_6 FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) local_insert, (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table) distributed_local_mixed) worker_subquery + key | value | age | key | value | age +--------------------------------------------------------------------- + 1 | 29 | 21 | 1 | 11 | 21 +(1 row) + +-- router CTE pushdown +WITH all_data AS (SELECT * FROM distributed_table WHERE key = 1) +SELECT + count(*) +FROM + distributed_table, all_data +WHERE + distributed_table.key = all_data.key AND distributed_table.key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table, (SELECT distributed_table_1.key, distributed_table_1.value, distributed_table_1.age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table_1 WHERE (distributed_table_1.key OPERATOR(pg_catalog.=) 1)) all_data WHERE ((distributed_table.key OPERATOR(pg_catalog.=) all_data.key) AND (distributed_table.key OPERATOR(pg_catalog.=) 1)) + count +--------------------------------------------------------------------- + 1 +(1 row) + +INSERT INTO reference_table VALUES (2); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 (key) VALUES (2) +INSERT INTO distributed_table VALUES (2, '29', 29); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 (key, value, age) VALUES (2, '29'::text, 29) +INSERT INTO second_distributed_table VALUES (2, '29'); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.second_distributed_table_1500008 (key, value) VALUES (2, '29'::text) +-- single shard that is not a local query followed by a local query +WITH all_data AS (SELECT * FROM second_distributed_table WHERE key = 2) +SELECT + distributed_table.key +FROM + distributed_table, all_data +WHERE + distributed_table.value = all_data.value AND distributed_table.key = 1 +ORDER BY + 1 DESC; +NOTICE: executing the command locally: SELECT distributed_table.key FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table, (SELECT second_distributed_table.key, second_distributed_table.value FROM local_shard_execution_replicated.second_distributed_table_1500008 second_distributed_table WHERE (second_distributed_table.key OPERATOR(pg_catalog.=) 2)) all_data WHERE ((distributed_table.value OPERATOR(pg_catalog.=) all_data.value) AND (distributed_table.key OPERATOR(pg_catalog.=) 1)) ORDER BY distributed_table.key DESC + key +--------------------------------------------------------------------- + 1 +(1 row) + +-- multi-shard CTE is followed by a query which could be executed locally, but +-- since the query started with a parallel query, it doesn't use local execution +-- note that if we allow Postgres to inline the CTE (e.g., not have the EXISTS +-- subquery), then it'd pushdown the filters and the query becomes single-shard, +-- locally executable query +WITH all_data AS (SELECT * FROM distributed_table) +SELECT + count(*) +FROM + distributed_table, all_data +WHERE + distributed_table.key = all_data.key AND distributed_table.key = 1 + AND EXISTS (SELECT * FROM all_data); +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT key, value, age FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table, (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) all_data WHERE ((distributed_table.key OPERATOR(pg_catalog.=) all_data.key) AND (distributed_table.key OPERATOR(pg_catalog.=) 1) AND (EXISTS (SELECT all_data_1.key, all_data_1.value, all_data_1.age FROM (SELECT intermediate_result.key, intermediate_result.value, intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value text, age bigint)) all_data_1))) + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- in pg12, the following CTE can be inlined, still the query becomes +-- a subquery that needs to be recursively planned and a parallel +-- query, so do not use local execution +WITH all_data AS (SELECT age FROM distributed_table) +SELECT + count(*) +FROM + distributed_table, all_data +WHERE + distributed_table.key = all_data.age AND distributed_table.key = 1; +NOTICE: executing the command locally: SELECT age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT age FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT age FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT age FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table, (SELECT intermediate_result.age FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(age bigint)) all_data WHERE ((distributed_table.key OPERATOR(pg_catalog.=) all_data.age) AND (distributed_table.key OPERATOR(pg_catalog.=) 1)) + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- get ready for the next commands +TRUNCATE reference_table, distributed_table, second_distributed_table; +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.reference_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.second_distributed_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.second_distributed_table_xxxxx CASCADE +-- local execution of returning of reference tables +INSERT INTO reference_table VALUES (1),(2),(3),(4),(5),(6) RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 AS citus_table_alias (key) VALUES (1), (2), (3), (4), (5), (6) RETURNING citus_table_alias.key + key +--------------------------------------------------------------------- + 1 + 2 + 3 + 4 + 5 + 6 +(6 rows) + +-- local execution of multi-row INSERTs +INSERT INTO distributed_table VALUES (1, '11',21), (5,'55',22) ON CONFLICT(key) DO UPDATE SET value = (EXCLUDED.value::int + 1)::text RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'11'::text,'21'::bigint), (5,'55'::text,'22'::bigint) ON CONFLICT(key) DO UPDATE SET value = (((excluded.value)::integer OPERATOR(pg_catalog.+) 1))::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 11 | 21 + 5 | 55 | 22 +(2 rows) + +-- distributed execution of multi-rows INSERTs, where executor +-- is smart enough to execute local tasks via local execution +INSERT INTO distributed_table VALUES (1, '11',21), (2,'22',22), (3,'33',33), (4,'44',44),(5,'55',55) ON CONFLICT(key) DO UPDATE SET value = (EXCLUDED.value::int + 1)::text RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'11'::text,'21'::bigint), (5,'55'::text,'55'::bigint) ON CONFLICT(key) DO UPDATE SET value = (((excluded.value)::integer OPERATOR(pg_catalog.+) 1))::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500002 AS citus_table_alias (key, value, age) VALUES (3,'33'::text,'33'::bigint), (4,'44'::text,'44'::bigint) ON CONFLICT(key) DO UPDATE SET value = (((excluded.value)::integer OPERATOR(pg_catalog.+) 1))::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 AS citus_table_alias (key, value, age) VALUES (2,'22'::text,'22'::bigint) ON CONFLICT(key) DO UPDATE SET value = (((excluded.value)::integer OPERATOR(pg_catalog.+) 1))::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 12 | 21 + 2 | 22 | 22 + 3 | 33 | 33 + 4 | 44 | 44 + 5 | 56 | 22 +(5 rows) + +PREPARE local_prepare_no_param AS SELECT count(*) FROM distributed_table WHERE key = 1; +PREPARE local_prepare_no_param_subquery AS +SELECT DISTINCT trim(value) FROM ( + SELECT value FROM distributed_table + WHERE + key IN (1, 6, 500, 701) + AND (select 2) > random() + order by 1 + limit 2 + ) t; +PREPARE local_prepare_param (int) AS SELECT count(*) FROM distributed_table WHERE key = $1; +PREPARE remote_prepare_param (int) AS SELECT count(*) FROM distributed_table WHERE key != $1; +BEGIN; + -- 8 local execution without params + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_no_param; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + -- 8 local execution without params and some subqueries + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + 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 + btrim +--------------------------------------------------------------------- + 12 +(1 row) + + -- 8 local executions with params + EXECUTE local_prepare_param(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(5); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 5) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + EXECUTE local_prepare_param(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(5); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.=) 5) + count +--------------------------------------------------------------------- + 1 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + EXECUTE local_prepare_param(6); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 6) + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- followed by a non-local execution + EXECUTE remote_prepare_param(1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 1) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 1) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 1) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 1) + count +--------------------------------------------------------------------- + 4 +(1 row) + +COMMIT; +PREPARE local_insert_prepare_no_param AS INSERT INTO distributed_table VALUES (1+0*random(), '11',21::int) ON CONFLICT(key) DO UPDATE SET value = '29' || '28' RETURNING *, key + 1, value || '30', age * 15; +PREPARE local_insert_prepare_param (int) AS INSERT INTO distributed_table VALUES ($1+0*random(), '11',21::int) ON CONFLICT(key) DO UPDATE SET value = '29' || '28' RETURNING *, key + 1, value || '30', age * 15; +BEGIN; + -- 8 local execution without params + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + -- 8 local executions with params + EXECUTE local_insert_prepare_param(1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 5 | 2928 | 22 | 6 | 292830 | 330 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 11 | 21 | 7 | 1130 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 1 | 2928 | 21 | 2 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 5 | 2928 | 22 | 6 | 292830 | 330 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 2928 | 21 | 7 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 2928 | 21 | 7 | 292830 | 315 +(1 row) + + EXECUTE local_insert_prepare_param(6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6, '11'::text, '21'::bigint) ON CONFLICT(key) DO UPDATE SET value = '2928'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age, (citus_table_alias.key OPERATOR(pg_catalog.+) 1), (citus_table_alias.value OPERATOR(pg_catalog.||) '30'::text), (citus_table_alias.age OPERATOR(pg_catalog.*) 15) + key | value | age | ?column? | ?column? | ?column? +--------------------------------------------------------------------- + 6 | 2928 | 21 | 7 | 292830 | 315 +(1 row) + + -- followed by a non-local execution + EXECUTE remote_prepare_param(2); +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 2) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 2) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 2) +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (key OPERATOR(pg_catalog.<>) 2) + count +--------------------------------------------------------------------- + 5 +(1 row) + +COMMIT; +PREPARE local_multi_row_insert_prepare_no_param AS + INSERT INTO distributed_table VALUES (1,'55', 21), (5,'15',33) ON CONFLICT (key) WHERE key > 3 and key < 4 DO UPDATE SET value = '88' || EXCLUDED.value; +PREPARE local_multi_row_insert_prepare_no_param_multi_shard AS + INSERT INTO distributed_table VALUES (6,'55', 21), (5,'15',33) ON CONFLICT (key) WHERE key > 3 AND key < 4 DO UPDATE SET value = '88' || EXCLUDED.value;; +PREPARE local_multi_row_insert_prepare_params(int,int) AS + INSERT INTO distributed_table VALUES ($1,'55', 21), ($2,'15',33) ON CONFLICT (key) WHERE key > 3 and key < 4 DO UPDATE SET value = '88' || EXCLUDED.value;; +INSERT INTO reference_table VALUES (11); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 (key) VALUES (11) +BEGIN; + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_no_param_multi_shard; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(6,5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(5,1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint), (1,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(5,6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(5,1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint), (1,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,6); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500003 AS citus_table_alias (key, value, age) VALUES (6,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + EXECUTE local_multi_row_insert_prepare_params(1,5); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1,'55'::text,'21'::bigint), (5,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) + -- one task is remote + EXECUTE local_multi_row_insert_prepare_params(5,11); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (5,'55'::text,'21'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 AS citus_table_alias (key, value, age) VALUES (11,'15'::text,'33'::bigint) ON CONFLICT(key) WHERE ((key OPERATOR(pg_catalog.>) 3) AND (key OPERATOR(pg_catalog.<) 4)) DO UPDATE SET value = ('88'::text OPERATOR(pg_catalog.||) excluded.value) +ROLLBACK; +-- failures of local execution should rollback both the +-- local execution and remote executions +-- fail on a local execution +BEGIN; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '100' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '100'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 100 | 21 +(1 row) + + UPDATE distributed_table SET value = '200'; +NOTICE: executing the command locally: UPDATE local_shard_execution_replicated.distributed_table_1500001 distributed_table SET value = '200'::text +NOTICE: executing the command locally: UPDATE local_shard_execution_replicated.distributed_table_1500002 distributed_table SET value = '200'::text +NOTICE: executing the command locally: UPDATE local_shard_execution_replicated.distributed_table_1500003 distributed_table SET value = '200'::text +NOTICE: executing the command locally: UPDATE local_shard_execution_replicated.distributed_table_1500004 distributed_table SET value = '200'::text + INSERT INTO distributed_table VALUES (1, '100',21) ON CONFLICT(key) DO UPDATE SET value = (1 / (100.0 - EXCLUDED.value::int))::text RETURNING *; +ERROR: division by zero +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +-- we've rollbacked everything +SELECT count(*) FROM distributed_table WHERE value = '200'; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- RETURNING should just work fine for reference tables +INSERT INTO reference_table VALUES (500) RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 (key) VALUES (500) RETURNING key + key +--------------------------------------------------------------------- + 500 +(1 row) + +DELETE FROM reference_table WHERE key = 500 RETURNING *; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.reference_table_1500000 reference_table WHERE (key OPERATOR(pg_catalog.=) 500) RETURNING key + key +--------------------------------------------------------------------- + 500 +(1 row) + +-- should be able to skip local execution even if in a sequential mode of execution +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO sequential ; + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '100' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '100'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 11 | 21 +(1 row) + +ROLLBACK; +-- sequential execution should just work fine after a local execution +BEGIN; + SET citus.multi_shard_modify_mode TO sequential ; + INSERT INTO distributed_table VALUES (1, '11',21) ON CONFLICT(key) DO UPDATE SET value = '100' RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 AS citus_table_alias (key, value, age) VALUES (1, '11'::text, 21) ON CONFLICT(key) DO UPDATE SET value = '100'::text RETURNING citus_table_alias.key, citus_table_alias.value, citus_table_alias.age + key | value | age +--------------------------------------------------------------------- + 1 | 100 | 21 +(1 row) + + DELETE FROM distributed_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table +ROLLBACK; +-- load some data so that foreign keys won't complain with the next tests +TRUNCATE reference_table CASCADE; +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.reference_table_xxxxx CASCADE +INSERT INTO reference_table SELECT i FROM generate_series(500, 600) i; +NOTICE: executing the copy locally for shard xxxxx +INSERT INTO distributed_table SELECT i, i::text, i % 10 + 25 FROM generate_series(500, 600) i; +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +-- show that both local, and mixed local-distributed executions +-- calculate rows processed correctly +BEGIN; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + DELETE FROM distributed_table WHERE value != '123123213123213'; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE (value OPERATOR(pg_catalog.<>) '123123213123213'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE (value OPERATOR(pg_catalog.<>) '123123213123213'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (value OPERATOR(pg_catalog.<>) '123123213123213'::text) +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE (value OPERATOR(pg_catalog.<>) '123123213123213'::text) +ROLLBACK; +BEGIN; + DELETE FROM reference_table WHERE key = 500 RETURNING *; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.reference_table_1500000 reference_table WHERE (key OPERATOR(pg_catalog.=) 500) RETURNING key + key +--------------------------------------------------------------------- + 500 +(1 row) + + DELETE FROM reference_table; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.reference_table_1500000 reference_table +ROLLBACK; +BEGIN; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true + count +--------------------------------------------------------------------- + 106 +(1 row) + +ROLLBACK; +BEGIN; + SET LOCAL client_min_messages TO INFO; + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true + count +--------------------------------------------------------------------- + 107 +(1 row) + + SET LOCAL client_min_messages TO LOG; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) +ROLLBACK; +-- probably not a realistic case since views are not very +-- well supported with MX +SET citus.enable_ddl_propagation TO OFF; +CREATE VIEW v_local_query_execution AS +SELECT * FROM distributed_table WHERE key = 500; +RESET citus.enable_ddl_propagation; +SELECT * FROM v_local_query_execution; +NOTICE: executing the command locally: SELECT key, value, age FROM (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (distributed_table.key OPERATOR(pg_catalog.=) 500)) v_local_query_execution + key | value | age +--------------------------------------------------------------------- + 500 | 500 | 25 +(1 row) + +-- similar test, but this time the view itself is a non-local +-- query, but the query on the view is local +SET citus.enable_ddl_propagation TO OFF; +CREATE VIEW v_local_query_execution_2 AS +SELECT * FROM distributed_table; +RESET citus.enable_ddl_propagation; +SELECT * FROM v_local_query_execution_2 WHERE key = 500; +NOTICE: executing the command locally: SELECT key, value, age FROM (SELECT distributed_table.key, distributed_table.value, distributed_table.age FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table) v_local_query_execution_2 WHERE (key OPERATOR(pg_catalog.=) 500) + key | value | age +--------------------------------------------------------------------- + 500 | 500 | 25 +(1 row) + +-- even if we switch from remote execution -> local execution, +-- we are able to use remote execution after rollback +BEGIN; + SAVEPOINT my_savepoint; + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true + count +--------------------------------------------------------------------- + 107 +(1 row) + + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + ROLLBACK TO SAVEPOINT my_savepoint; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) +COMMIT; +-- even if we switch from local execution -> remote execution, +-- we are able to use local execution after rollback +BEGIN; + SAVEPOINT my_savepoint; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) + SELECT count(*) FROM distributed_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table WHERE true + count +--------------------------------------------------------------------- + 106 +(1 row) + + ROLLBACK TO SAVEPOINT my_savepoint; + DELETE FROM distributed_table WHERE key = 500; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table WHERE (key OPERATOR(pg_catalog.=) 500) +COMMIT; +-- sanity check: local execution on partitions +INSERT INTO collections_list (collection_id) VALUES (0) RETURNING *; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500011 (key, ser, collection_id) VALUES ('3940649673949185'::bigint, '3940649673949185'::bigint, 0) RETURNING key, ser, ts, collection_id, value + key | ser | ts | collection_id | value +--------------------------------------------------------------------- + 3940649673949185 | 3940649673949185 | | 0 | +(1 row) + +BEGIN; + INSERT INTO collections_list (key, collection_id) VALUES (1,0); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500009 (key, ser, collection_id) VALUES ('1'::bigint, '3940649673949186'::bigint, 0) + SELECT count(*) FROM collections_list_0 WHERE key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.collections_list_0_1500013 collections_list_0 WHERE (key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + SELECT count(*) FROM collections_list; +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.collections_list_1500009 collections_list WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.collections_list_1500010 collections_list WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.collections_list_1500011 collections_list WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.collections_list_1500012 collections_list WHERE true + count +--------------------------------------------------------------------- + 2 +(1 row) + + SELECT * FROM collections_list ORDER BY 1,2,3,4; +NOTICE: executing the command locally: SELECT key, ser, ts, collection_id, value FROM local_shard_execution_replicated.collections_list_1500009 collections_list WHERE true +NOTICE: executing the command locally: SELECT key, ser, ts, collection_id, value FROM local_shard_execution_replicated.collections_list_1500010 collections_list WHERE true +NOTICE: executing the command locally: SELECT key, ser, ts, collection_id, value FROM local_shard_execution_replicated.collections_list_1500011 collections_list WHERE true +NOTICE: executing the command locally: SELECT key, ser, ts, collection_id, value FROM local_shard_execution_replicated.collections_list_1500012 collections_list WHERE true + key | ser | ts | collection_id | value +--------------------------------------------------------------------- + 1 | 3940649673949186 | | 0 | + 3940649673949185 | 3940649673949185 | | 0 | +(2 rows) + +COMMIT; +TRUNCATE collections_list; +-- make sure that even if local execution is used, the sequence values +-- are generated locally +SET citus.enable_ddl_propagation TO OFF; +ALTER SEQUENCE collections_list_key_seq NO MINVALUE NO MAXVALUE; +RESET citus.enable_ddl_propagation; +PREPARE serial_prepared_local AS INSERT INTO collections_list (collection_id) VALUES (0) RETURNING key, ser; +SELECT setval('collections_list_key_seq', 4); + setval +--------------------------------------------------------------------- + 4 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500009 (key, ser, collection_id) VALUES ('5'::bigint, '3940649673949187'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 5 | 3940649673949187 +(1 row) + +SELECT setval('collections_list_key_seq', 5); + setval +--------------------------------------------------------------------- + 5 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500011 (key, ser, collection_id) VALUES ('6'::bigint, '3940649673949188'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 6 | 3940649673949188 +(1 row) + +SELECT setval('collections_list_key_seq', 499); + setval +--------------------------------------------------------------------- + 499 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500011 (key, ser, collection_id) VALUES ('500'::bigint, '3940649673949189'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 500 | 3940649673949189 +(1 row) + +SELECT setval('collections_list_key_seq', 700); + setval +--------------------------------------------------------------------- + 700 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500009 (key, ser, collection_id) VALUES ('701'::bigint, '3940649673949190'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 701 | 3940649673949190 +(1 row) + +SELECT setval('collections_list_key_seq', 708); + setval +--------------------------------------------------------------------- + 708 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500011 (key, ser, collection_id) VALUES ('709'::bigint, '3940649673949191'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 709 | 3940649673949191 +(1 row) + +SELECT setval('collections_list_key_seq', 709); + setval +--------------------------------------------------------------------- + 709 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500009 (key, ser, collection_id) VALUES ('710'::bigint, '3940649673949192'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 710 | 3940649673949192 +(1 row) + +-- get ready for the next executions +DELETE FROM collections_list WHERE key IN (5,6); +SELECT setval('collections_list_key_seq', 4); + setval +--------------------------------------------------------------------- + 4 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500009 (key, ser, collection_id) VALUES ('5'::bigint, '3940649673949193'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 5 | 3940649673949193 +(1 row) + +SELECT setval('collections_list_key_seq', 5); + setval +--------------------------------------------------------------------- + 5 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500011 (key, ser, collection_id) VALUES ('6'::bigint, '3940649673949194'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 6 | 3940649673949194 +(1 row) + +-- and, one remote test +SELECT setval('collections_list_key_seq', 10); + setval +--------------------------------------------------------------------- + 10 +(1 row) + +EXECUTE serial_prepared_local; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.collections_list_1500012 (key, ser, collection_id) VALUES ('11'::bigint, '3940649673949195'::bigint, 0) RETURNING key, ser + key | ser +--------------------------------------------------------------------- + 11 | 3940649673949195 +(1 row) + +-- the final queries for the following CTEs are going to happen on the intermediate results only +-- one of them will be executed remotely, and the other is locally +-- Citus currently doesn't allow using task_assignment_policy for intermediate results +WITH distributed_local_mixed AS (INSERT INTO reference_table VALUES (1000) RETURNING *) SELECT * FROM distributed_local_mixed; +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 (key) VALUES (1000) RETURNING key +NOTICE: executing the command locally: SELECT key FROM (SELECT intermediate_result.key FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer)) distributed_local_mixed + key +--------------------------------------------------------------------- + 1000 +(1 row) + +-- clean the table for the next tests +SET search_path TO local_shard_execution_replicated; +TRUNCATE distributed_table CASCADE; +-- load some data on a remote shard +INSERT INTO reference_table (key) VALUES (1), (2); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 AS citus_table_alias (key) VALUES (1), (2) +INSERT INTO distributed_table (key) VALUES (2); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500004 (key) VALUES (2) +BEGIN; + -- local execution followed by a distributed query + INSERT INTO distributed_table (key) VALUES (1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.distributed_table_1500001 (key) VALUES (1) + DELETE FROM distributed_table RETURNING key; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table RETURNING key +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500002 distributed_table RETURNING key +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500003 distributed_table RETURNING key +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.distributed_table_1500004 distributed_table RETURNING key + key +--------------------------------------------------------------------- + 1 + 2 +(2 rows) + +COMMIT; +-- a similar test with a reference table +TRUNCATE reference_table CASCADE; +NOTICE: executing the command locally: TRUNCATE TABLE local_shard_execution_replicated.reference_table_xxxxx CASCADE +-- load some data on a remote shard +INSERT INTO reference_table (key) VALUES (2); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 (key) VALUES (2) +BEGIN; + -- local execution followed by a distributed query + INSERT INTO reference_table (key) VALUES (1); +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.reference_table_1500000 (key) VALUES (1) + DELETE FROM reference_table RETURNING key; +NOTICE: executing the command locally: DELETE FROM local_shard_execution_replicated.reference_table_1500000 reference_table RETURNING key + key +--------------------------------------------------------------------- + 1 + 2 +(2 rows) + +COMMIT; +-- however complex the query, local execution can handle +SET client_min_messages TO LOG; +SET citus.log_local_commands TO ON; +WITH cte_1 AS + (SELECT * + FROM + (WITH cte_1 AS + (SELECT * + FROM distributed_table + WHERE key = 1) SELECT * + FROM cte_1) AS foo) +SELECT count(*) +FROM cte_1 +JOIN distributed_table USING (key) +WHERE distributed_table.key = 1 + AND distributed_table.key IN + (SELECT key + FROM distributed_table + WHERE key = 1); +NOTICE: executing the command locally: SELECT count(*) AS count FROM ((SELECT foo.key, foo.value, foo.age FROM (SELECT cte_1_1.key, cte_1_1.value, cte_1_1.age FROM (SELECT distributed_table_1.key, distributed_table_1.value, distributed_table_1.age FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table_1 WHERE (distributed_table_1.key OPERATOR(pg_catalog.=) 1)) cte_1_1) foo) cte_1 JOIN local_shard_execution_replicated.distributed_table_1500001 distributed_table(key, value, age) USING (key)) WHERE ((distributed_table.key OPERATOR(pg_catalog.=) 1) AND (distributed_table.key OPERATOR(pg_catalog.=) ANY (SELECT distributed_table_1.key FROM local_shard_execution_replicated.distributed_table_1500001 distributed_table_1 WHERE (distributed_table_1.key OPERATOR(pg_catalog.=) 1)))) + count +--------------------------------------------------------------------- + 0 +(1 row) + +RESET client_min_messages; +RESET citus.log_local_commands; +\c - - - :master_port +SET citus.next_shard_id TO 1501000; +-- test both local and remote execution with custom type +SET citus.shard_replication_factor TO 2; +SET search_path TO local_shard_execution_replicated; +CREATE TYPE invite_resp AS ENUM ('yes', 'no', 'maybe'); +CREATE TABLE event_responses ( + event_id int, + user_id int, + response invite_resp, + primary key (event_id, user_id) +); +SELECT create_distributed_table('event_responses', 'event_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO event_responses VALUES (1, 1, 'yes'), (2, 2, 'yes'), (3, 3, 'no'), (4, 4, 'no'); +CREATE OR REPLACE FUNCTION regular_func(p invite_resp) +RETURNS int AS $$ +DECLARE + q1Result INT; + q2Result INT; + q3Result INT; +BEGIN +SELECT count(*) INTO q1Result FROM event_responses WHERE response = $1; +SELECT count(*) INTO q2Result FROM event_responses e1 LEFT JOIN event_responses e2 USING (event_id) WHERE e2.response = $1; +SELECT count(*) INTO q3Result FROM (SELECT * FROM event_responses WHERE response = $1 LIMIT 5) as foo; +RETURN q3Result+q2Result+q1Result; +END; +$$ LANGUAGE plpgsql; +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +SELECT regular_func('yes'); + regular_func +--------------------------------------------------------------------- + 6 +(1 row) + +CREATE OR REPLACE PROCEDURE regular_procedure(p invite_resp) +AS $$ +BEGIN +PERFORM * FROM event_responses WHERE response = $1 ORDER BY 1 DESC, 2 DESC, 3 DESC; +PERFORM * FROM event_responses e1 LEFT JOIN event_responses e2 USING (event_id) WHERE e2.response = $1 ORDER BY 1 DESC, 2 DESC, 3 DESC, 4 DESC; +PERFORM * FROM (SELECT * FROM event_responses WHERE response = $1 LIMIT 5) as foo ORDER BY 1 DESC, 2 DESC, 3 DESC; +END; +$$ LANGUAGE plpgsql; +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +CALL regular_procedure('no'); +PREPARE multi_shard_no_dist_key(invite_resp) AS select * from event_responses where response = $1::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 1; +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_no_dist_key('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +PREPARE multi_shard_with_dist_key(int, invite_resp) AS select * from event_responses where event_id > $1 AND response = $2::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 1; +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +EXECUTE multi_shard_with_dist_key(1, 'yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes +(1 row) + +PREPARE query_pushdown_no_dist_key(invite_resp) AS select * from event_responses e1 LEFT JOIN event_responses e2 USING(event_id) where e1.response = $1::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC, 4 DESC LIMIT 1; +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +EXECUTE query_pushdown_no_dist_key('yes'); + event_id | user_id | response | user_id | response +--------------------------------------------------------------------- + 2 | 2 | yes | 2 | yes +(1 row) + +PREPARE insert_select_via_coord(invite_resp) AS INSERT INTO event_responses SELECT * FROM event_responses where response = $1::invite_resp LIMIT 1 ON CONFLICT (event_id, user_id) DO NOTHING ; +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +EXECUTE insert_select_via_coord('yes'); +PREPARE insert_select_pushdown(invite_resp) AS INSERT INTO event_responses SELECT * FROM event_responses where response = $1::invite_resp ON CONFLICT (event_id, user_id) DO NOTHING; +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +EXECUTE insert_select_pushdown('yes'); +PREPARE router_select_with_no_dist_key_filter(invite_resp) AS select * from event_responses where event_id = 1 AND response = $1::invite_resp ORDER BY 1 DESC, 2 DESC, 3 DESC LIMIT 1; +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +EXECUTE router_select_with_no_dist_key_filter('yes'); + event_id | user_id | response +--------------------------------------------------------------------- + 1 | 1 | yes +(1 row) + +-- rest of the tests assume the table is empty +TRUNCATE event_responses; +CREATE OR REPLACE PROCEDURE register_for_event(p_event_id int, p_user_id int, p_choice invite_resp) +LANGUAGE plpgsql AS $fn$ +BEGIN + INSERT INTO local_shard_execution_replicated.event_responses VALUES (p_event_id, p_user_id, p_choice) + ON CONFLICT (event_id, user_id) + DO UPDATE SET response = EXCLUDED.response; + + PERFORM count(*) FROM local_shard_execution_replicated.event_responses WHERE event_id = p_event_id; + + PERFORM count(*) FROM local_shard_execution_replicated.event_responses WHERE event_id = p_event_id AND false; + + UPDATE local_shard_execution_replicated.event_responses SET response = p_choice WHERE event_id = p_event_id; + +END; +$fn$; +SELECT create_distributed_function('register_for_event(int,int,invite_resp)'); +NOTICE: procedure local_shard_execution_replicated.register_for_event is already distributed +DETAIL: Citus distributes procedures with CREATE [PROCEDURE|FUNCTION|AGGREGATE] commands + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +-- call 8 times to make sure it works after the 5th time(postgres binds values after the 5th time and Citus 2nd time) +-- after 6th, the local execution caches the local plans and uses it +-- execute it both locally and remotely +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +\c - - - :worker_2_port +SET search_path TO local_shard_execution_replicated; +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +CALL register_for_event(16, 1, 'yes'); +-- values 16, 17 and 19 hits the same +-- shard, so we're re-using the same cached +-- plans per statement across different distribution +-- key values +CALL register_for_event(17, 1, 'yes'); +CALL register_for_event(19, 1, 'yes'); +CALL register_for_event(17, 1, 'yes'); +CALL register_for_event(19, 1, 'yes'); +-- should work fine if the logs are enabled +\set VERBOSITY terse +SET citus.log_local_commands TO ON; +SET client_min_messages TO DEBUG2; +CALL register_for_event(19, 1, 'yes'); +DEBUG: stored procedure does not have co-located tables +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.event_responses_1501001 AS citus_table_alias (event_id, user_id, response) VALUES (19, 1, 'yes'::local_shard_execution_replicated.invite_resp) ON CONFLICT(event_id, user_id) DO UPDATE SET response = excluded.response +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.event_responses_1501001 event_responses WHERE (event_id OPERATOR(pg_catalog.=) 19) +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT NULL::integer AS event_id, NULL::integer AS user_id, NULL::local_shard_execution_replicated.invite_resp AS response WHERE false) event_responses(event_id, user_id, response) WHERE ((event_id OPERATOR(pg_catalog.=) 19) AND false) +NOTICE: executing the command locally: UPDATE local_shard_execution_replicated.event_responses_1501001 event_responses SET response = 'yes'::local_shard_execution_replicated.invite_resp WHERE (event_id OPERATOR(pg_catalog.=) 19) +-- should be fine even if no parameters exists in the query +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.event_responses_1501001 event_responses WHERE (event_id OPERATOR(pg_catalog.=) 16) + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: SELECT count(*) AS count FROM local_shard_execution_replicated.event_responses_1501001 event_responses WHERE (event_id OPERATOR(pg_catalog.=) 16) + count +--------------------------------------------------------------------- + 1 +(1 row) + +UPDATE event_responses SET response = 'no' WHERE event_id = 16; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: UPDATE local_shard_execution_replicated.event_responses_1501001 event_responses SET response = 'no'::local_shard_execution_replicated.invite_resp WHERE (event_id OPERATOR(pg_catalog.=) 16) +INSERT INTO event_responses VALUES (16, 666, 'maybe') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.event_responses_1501001 AS citus_table_alias (event_id, user_id, response) VALUES (16, 666, 'maybe'::local_shard_execution_replicated.invite_resp) ON CONFLICT(event_id, user_id) DO UPDATE SET response = excluded.response RETURNING citus_table_alias.event_id, citus_table_alias.user_id, citus_table_alias.response + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe +(1 row) + +-- multi row INSERTs hitting the same shard +INSERT INTO event_responses VALUES (16, 666, 'maybe'), (17, 777, 'no') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan +NOTICE: executing the command locally: INSERT INTO local_shard_execution_replicated.event_responses_1501001 AS citus_table_alias (event_id, user_id, response) VALUES (16,666,'maybe'::local_shard_execution_replicated.invite_resp), (17,777,'no'::local_shard_execution_replicated.invite_resp) ON CONFLICT(event_id, user_id) DO UPDATE SET response = excluded.response RETURNING citus_table_alias.event_id, citus_table_alias.user_id, citus_table_alias.response + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe + 17 | 777 | no +(2 rows) + +-- now, similar tests with some settings changed +SET citus.enable_local_execution TO false; +SET citus.enable_fast_path_router_planner TO false; +CALL register_for_event(19, 1, 'yes'); +DEBUG: stored procedure does not have co-located tables +-- should be fine even if no parameters exists in the query +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM event_responses WHERE event_id = 16; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 + count +--------------------------------------------------------------------- + 2 +(1 row) + +UPDATE event_responses SET response = 'no' WHERE event_id = 16; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 +INSERT INTO event_responses VALUES (16, 666, 'maybe') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan +DEBUG: query has a single distribution column value: 16 + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe +(1 row) + +-- multi row INSERTs hitting the same shard +INSERT INTO event_responses VALUES (16, 666, 'maybe'), (17, 777, 'no') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +DEBUG: Creating router plan + event_id | user_id | response +--------------------------------------------------------------------- + 16 | 666 | maybe + 17 | 777 | no +(2 rows) + +-- not allow commands over the workers when user disables +SET citus.allow_modifications_from_workers_to_replicated_tables TO false; +INSERT INTO event_responses VALUES (16, 666, 'maybe'), (17, 777, 'no') +ON CONFLICT (event_id, user_id) +DO UPDATE SET response = EXCLUDED.response RETURNING *; +ERROR: modifications via the worker nodes are not allowed for replicated tables such as reference tables or hash distributed tables with replication factor greater than 1. +\c - - - :master_port +SET client_min_messages TO ERROR; +SET search_path TO public; +DROP SCHEMA local_shard_execution_replicated CASCADE; diff --git a/src/test/regress/expected/local_table_join.out b/src/test/regress/expected/local_table_join.out index 90737a2ed..99ab23efc 100644 --- a/src/test/regress/expected/local_table_join.out +++ b/src/test/regress/expected/local_table_join.out @@ -1357,7 +1357,7 @@ select typdefault from ( where typdefault > 'a' limit 1) as subq_0 where ( - select true from pg_catalog.pg_am limit 1 + select true as bool from pg_catalog.pg_am limit 1 ) ) as subq_1 ) as subq_2; @@ -1387,7 +1387,7 @@ select typdefault from ( where typdefault > 'a' limit 1) as subq_0 where ( - select true from pg_catalog.pg_am limit 1 + select true as bool from pg_catalog.pg_am limit 1 ) ) as subq_1 ) as subq_2; diff --git a/src/test/regress/expected/logical_replication.out b/src/test/regress/expected/logical_replication.out index 3218387f3..866df4037 100644 --- a/src/test/regress/expected/logical_replication.out +++ b/src/test/regress/expected/logical_replication.out @@ -21,16 +21,21 @@ NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipp 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 SET search_path TO logical_replication; CREATE PUBLICATION citus_shard_move_publication_:postgres_oid FOR TABLE dist_6830000; \c - - - :master_port SET search_path TO logical_replication; -\set connection_string '\'user=postgres host=localhost port=' :worker_1_port '\'' +CREATE TABLE dist_6830000( + id bigserial PRIMARY KEY +); +\set connection_string '\'user=postgres host=localhost port=' :worker_1_port ' dbname=regression\'' CREATE SUBSCRIPTION citus_shard_move_subscription_:postgres_oid CONNECTION :connection_string PUBLICATION citus_shard_move_publication_:postgres_oid - WITH (slot_name=citus_shard_move_slot_:postgres_oid); + WITH (enabled=false, slot_name=citus_shard_move_slot_:postgres_oid); NOTICE: created replication slot "citus_shard_move_slot_10" on publisher SELECT count(*) from pg_subscription; count diff --git a/src/test/regress/expected/multi_alter_table_add_constraints.out b/src/test/regress/expected/multi_alter_table_add_constraints.out index e96326c18..2833facce 100644 --- a/src/test/regress/expected/multi_alter_table_add_constraints.out +++ b/src/test/regress/expected/multi_alter_table_add_constraints.out @@ -279,6 +279,19 @@ SELECT create_distributed_table('products', 'product_no'); ALTER TABLE products ADD CONSTRAINT exc_name EXCLUDE USING btree (name with =); ERROR: cannot create constraint on "products" DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +-- check that we can disable the constraint check for EXCLUDE +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +ALTER TABLE products ADD CONSTRAINT exc_name EXCLUDE USING btree (name with =); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',12.0); +ERROR: conflicting key value violates exclusion constraint "exc_name_1450103" +DETAIL: Key (name)=(boat) conflicts with existing key (name)=(boat). +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; -- We can add composite exclusion ALTER TABLE products ADD CONSTRAINT exc_pno_name EXCLUDE USING btree (product_no with =, name with =); -- 4th command will error out since it conflicts with exc_pno_name constraint @@ -453,6 +466,46 @@ INSERT INTO products VALUES(1,'product_1', 5); -- DDL should pick the right connections after a single INSERT ALTER TABLE products ADD CONSTRAINT unn_pno UNIQUE(product_no); ROLLBACK; +-- check that we can disable the constraint check for CREATE UNIQUE INDEX +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +CREATE UNIQUE INDEX ON products (name, price); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',10.0); +ERROR: duplicate key value violates unique constraint "products_name_price_idx_1450203" +DETAIL: Key (name, price)=(boat, 10.0) already exists. +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +-- check that we can disable the constraint check for CREATE UNIQUE INDEX CONCURRENTLY +SET citus.allow_unsafe_constraints TO on; +CREATE UNIQUE INDEX CONCURRENTLY product_idx ON products (name, price); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',10.0); +ERROR: duplicate key value violates unique constraint "product_idx_1450203" +DETAIL: Key (name, price)=(boat, 10.0) already exists. +CONTEXT: while executing command on localhost:xxxxx +DROP INDEX product_idx; +TRUNCATE products; +RESET citus.allow_unsafe_constraints; +-- check that we can disable the constraint check for ADD CONSTRAINT .. PRIMARY KEY +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +ALTER TABLE products ADD CONSTRAINT products_pk PRIMARY KEY (name, price); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',10.0); +ERROR: duplicate key value violates unique constraint "products_pk_1450203" +DETAIL: Key (name, price)=(boat, 10.0) already exists. +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; BEGIN; -- Add constraints ALTER TABLE products ADD CONSTRAINT unn_pno UNIQUE(product_no); diff --git a/src/test/regress/expected/multi_colocated_shard_rebalance_0.out b/src/test/regress/expected/multi_colocated_shard_rebalance_0.out deleted file mode 100644 index 95871bc31..000000000 --- a/src/test/regress/expected/multi_colocated_shard_rebalance_0.out +++ /dev/null @@ -1,788 +0,0 @@ --- --- MULTI_COLOCATED_SHARD_REBALANCE --- -ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 13000000; -SET citus.shard_count TO 6; -SET citus.shard_replication_factor TO 1; --- create distributed tables -CREATE TABLE table1_group1 ( id int PRIMARY KEY); -SELECT create_distributed_table('table1_group1', 'id', 'hash'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -CREATE TABLE table2_group1 ( id int ); -SELECT create_distributed_table('table2_group1', 'id', 'hash'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -SET citus.shard_count TO 8; -CREATE TABLE table5_groupX ( id int ); -SELECT create_distributed_table('table5_groupX', 'id', 'hash'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -CREATE TABLE table6_append ( id int ); -SELECT master_create_distributed_table('table6_append', 'id', 'append'); - master_create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -SELECT master_create_empty_shard('table6_append'); - master_create_empty_shard ---------------------------------------------------------------------- - 13000020 -(1 row) - -SELECT master_create_empty_shard('table6_append'); - master_create_empty_shard ---------------------------------------------------------------------- - 13000021 -(1 row) - --- Mark tables as non-mx tables, in order to be able to test master_copy_shard_placement -UPDATE pg_dist_partition SET repmodel='c' WHERE logicalrelid IN - ('table1_group1'::regclass, 'table2_group1'::regclass, 'table5_groupX'::regclass); --- test copy --- test copying colocated shards --- status before shard copy -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'table1_group1'::regclass) -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000000 | table1_group1 | 57637 - 13000001 | table1_group1 | 57638 - 13000002 | table1_group1 | 57637 - 13000003 | table1_group1 | 57638 - 13000004 | table1_group1 | 57637 - 13000005 | table1_group1 | 57638 - 13000006 | table2_group1 | 57637 - 13000007 | table2_group1 | 57638 - 13000008 | table2_group1 | 57637 - 13000009 | table2_group1 | 57638 - 13000010 | table2_group1 | 57637 - 13000011 | table2_group1 | 57638 -(12 rows) - --- try to copy colocated shards without a replica identity -SELECT master_copy_shard_placement(13000000, 'localhost', :worker_1_port, 'localhost', :worker_2_port, false); - master_copy_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- copy colocated shards -SELECT master_copy_shard_placement(13000000, 'localhost', :worker_1_port, 'localhost', :worker_2_port, false, 'force_logical'); -ERROR: shard xxxxx already exist in target placement --- status after shard copy -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'table1_group1'::regclass) -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000000 | table1_group1 | 57637 - 13000000 | table1_group1 | 57638 - 13000001 | table1_group1 | 57638 - 13000002 | table1_group1 | 57637 - 13000003 | table1_group1 | 57638 - 13000004 | table1_group1 | 57637 - 13000005 | table1_group1 | 57638 - 13000006 | table2_group1 | 57637 - 13000006 | table2_group1 | 57638 - 13000007 | table2_group1 | 57638 - 13000008 | table2_group1 | 57637 - 13000009 | table2_group1 | 57638 - 13000010 | table2_group1 | 57637 - 13000011 | table2_group1 | 57638 -(14 rows) - --- also connect worker to verify we successfully copied given shard (and other colocated shards) -\c - - - :worker_2_port -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.table1_group1_13000000'::regclass; - Column | Type | Modifiers ---------------------------------------------------------------------- - id | integer | not null -(1 row) - -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.table2_group1_13000006'::regclass; - Column | Type | Modifiers ---------------------------------------------------------------------- - id | integer | -(1 row) - -\c - - - :master_port --- copy colocated shards again to see error message -SELECT master_copy_shard_placement(13000000, 'localhost', :worker_1_port, 'localhost', :worker_2_port, false, 'force_logical'); -ERROR: shard xxxxx already exist in target placement --- test copying NOT colocated shard --- status before shard copy -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table5_groupX'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000012 | table5_groupx | 57637 - 13000013 | table5_groupx | 57638 - 13000014 | table5_groupx | 57637 - 13000015 | table5_groupx | 57638 - 13000016 | table5_groupx | 57637 - 13000017 | table5_groupx | 57638 - 13000018 | table5_groupx | 57637 - 13000019 | table5_groupx | 57638 -(8 rows) - --- copy NOT colocated shard -SELECT master_copy_shard_placement(13000012, 'localhost', :worker_1_port, 'localhost', :worker_2_port, false, 'force_logical'); - master_copy_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- status after shard copy -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table5_groupX'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000012 | table5_groupx | 57637 - 13000012 | table5_groupx | 57638 - 13000013 | table5_groupx | 57638 - 13000014 | table5_groupx | 57637 - 13000015 | table5_groupx | 57638 - 13000016 | table5_groupx | 57637 - 13000017 | table5_groupx | 57638 - 13000018 | table5_groupx | 57637 - 13000019 | table5_groupx | 57638 -(9 rows) - --- test copying shard in append distributed table --- status before shard copy -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table6_append'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000020 | table6_append | 57638 - 13000021 | table6_append | 57637 -(2 rows) - --- copy shard in append distributed table -SELECT master_copy_shard_placement(13000020, 'localhost', :worker_2_port, 'localhost', :worker_1_port, false, 'force_logical'); - master_copy_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- status after shard copy -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table6_append'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000020 | table6_append | 57637 - 13000020 | table6_append | 57638 - 13000021 | table6_append | 57637 -(3 rows) - --- test move --- test moving colocated shards --- status before shard move -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'table1_group1'::regclass) -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000000 | table1_group1 | 57637 - 13000000 | table1_group1 | 57638 - 13000001 | table1_group1 | 57638 - 13000002 | table1_group1 | 57637 - 13000003 | table1_group1 | 57638 - 13000004 | table1_group1 | 57637 - 13000005 | table1_group1 | 57638 - 13000006 | table2_group1 | 57637 - 13000006 | table2_group1 | 57638 - 13000007 | table2_group1 | 57638 - 13000008 | table2_group1 | 57637 - 13000009 | table2_group1 | 57638 - 13000010 | table2_group1 | 57637 - 13000011 | table2_group1 | 57638 -(14 rows) - --- move colocated shards -SELECT master_move_shard_placement(13000001, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- status after shard move -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'table1_group1'::regclass) -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000000 | table1_group1 | 57637 - 13000000 | table1_group1 | 57638 - 13000001 | table1_group1 | 57637 - 13000002 | table1_group1 | 57637 - 13000003 | table1_group1 | 57638 - 13000004 | table1_group1 | 57637 - 13000005 | table1_group1 | 57638 - 13000006 | table2_group1 | 57637 - 13000006 | table2_group1 | 57638 - 13000007 | table2_group1 | 57637 - 13000008 | table2_group1 | 57637 - 13000009 | table2_group1 | 57638 - 13000010 | table2_group1 | 57637 - 13000011 | table2_group1 | 57638 -(14 rows) - --- also connect worker to verify we successfully moved given shard (and other colocated shards) -\c - - - :worker_1_port -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.table1_group1_13000001'::regclass; - Column | Type | Modifiers ---------------------------------------------------------------------- - id | integer | not null -(1 row) - -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.table2_group1_13000007'::regclass; - Column | Type | Modifiers ---------------------------------------------------------------------- - id | integer | -(1 row) - -\c - - - :master_port --- test moving NOT colocated shard --- status before shard move -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table5_groupX'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000012 | table5_groupx | 57637 - 13000012 | table5_groupx | 57638 - 13000013 | table5_groupx | 57638 - 13000014 | table5_groupx | 57637 - 13000015 | table5_groupx | 57638 - 13000016 | table5_groupx | 57637 - 13000017 | table5_groupx | 57638 - 13000018 | table5_groupx | 57637 - 13000019 | table5_groupx | 57638 -(9 rows) - --- move NOT colocated shard -SELECT master_move_shard_placement(13000013, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- status after shard move -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table5_groupX'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000012 | table5_groupx | 57637 - 13000012 | table5_groupx | 57638 - 13000013 | table5_groupx | 57637 - 13000014 | table5_groupx | 57637 - 13000015 | table5_groupx | 57638 - 13000016 | table5_groupx | 57637 - 13000017 | table5_groupx | 57638 - 13000018 | table5_groupx | 57637 - 13000019 | table5_groupx | 57638 -(9 rows) - --- test moving shard in append distributed table --- status before shard move -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table6_append'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000020 | table6_append | 57637 - 13000020 | table6_append | 57638 - 13000021 | table6_append | 57637 -(3 rows) - --- move shard in append distributed table -SELECT master_move_shard_placement(13000021, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- status after shard move -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - p.logicalrelid = 'table6_append'::regclass -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000020 | table6_append | 57637 - 13000020 | table6_append | 57638 - 13000021 | table6_append | 57638 -(3 rows) - --- try to move shard from wrong node -SELECT master_move_shard_placement(13000021, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); -ERROR: could not find placement matching "localhost:xxxxx" -HINT: Confirm the placement still exists and try again. --- test shard move with foreign constraints -DROP TABLE IF EXISTS table1_group1, table2_group1; -SET citus.shard_count TO 6; -SET citus.shard_replication_factor TO 1; --- create distributed tables -CREATE TABLE table1_group1 ( id int PRIMARY KEY); -SELECT create_distributed_table('table1_group1', 'id', 'hash'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -CREATE TABLE table2_group1 ( id int, table1_id int, FOREIGN KEY(table1_id) REFERENCES table1_group1(id)); -SELECT create_distributed_table('table2_group1', 'table1_id', 'hash'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - --- Mark the tables as non-mx tables -UPDATE pg_dist_partition SET repmodel='c' WHERE logicalrelid IN - ('table1_group1'::regclass, 'table2_group1'::regclass); --- status before shard rebalance -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'table1_group1'::regclass) -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000022 | table1_group1 | 57637 - 13000023 | table1_group1 | 57638 - 13000024 | table1_group1 | 57637 - 13000025 | table1_group1 | 57638 - 13000026 | table1_group1 | 57637 - 13000027 | table1_group1 | 57638 - 13000028 | table2_group1 | 57637 - 13000029 | table2_group1 | 57638 - 13000030 | table2_group1 | 57637 - 13000031 | table2_group1 | 57638 - 13000032 | table2_group1 | 57637 - 13000033 | table2_group1 | 57638 -(12 rows) - -SELECT master_move_shard_placement(13000022, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'block_writes'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- status after shard rebalance -SELECT s.shardid, s.logicalrelid::regclass, sp.nodeport -FROM - pg_dist_partition p, pg_dist_shard s, pg_dist_shard_placement sp -WHERE - p.logicalrelid = s.logicalrelid AND - s.shardid = sp.shardid AND - colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'table1_group1'::regclass) -ORDER BY s.shardid, sp.nodeport; - shardid | logicalrelid | nodeport ---------------------------------------------------------------------- - 13000022 | table1_group1 | 57638 - 13000023 | table1_group1 | 57638 - 13000024 | table1_group1 | 57637 - 13000025 | table1_group1 | 57638 - 13000026 | table1_group1 | 57637 - 13000027 | table1_group1 | 57638 - 13000028 | table2_group1 | 57638 - 13000029 | table2_group1 | 57638 - 13000030 | table2_group1 | 57637 - 13000031 | table2_group1 | 57638 - 13000032 | table2_group1 | 57637 - 13000033 | table2_group1 | 57638 -(12 rows) - --- also connect worker to verify we successfully moved given shard (and other colocated shards) -\c - - - :worker_2_port -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.table1_group1_13000022'::regclass; - Column | Type | Modifiers ---------------------------------------------------------------------- - id | integer | not null -(1 row) - -SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='public.table2_group1_13000028'::regclass; - Column | Type | Modifiers ---------------------------------------------------------------------- - id | integer | - table1_id | integer | -(2 rows) - --- make sure that we've created the foreign keys -SELECT "Constraint", "Definition" FROM table_fkeys; - Constraint | Definition ---------------------------------------------------------------------- - pg_dist_poolinfo_nodeid_fkey | FOREIGN KEY (nodeid) REFERENCES pg_dist_node(nodeid) - table2_group1_table1_id_fkey_13000028 | FOREIGN KEY (table1_id) REFERENCES table1_group1_13000022(id) - table2_group1_table1_id_fkey_13000029 | FOREIGN KEY (table1_id) REFERENCES table1_group1_13000023(id) - table2_group1_table1_id_fkey_13000031 | FOREIGN KEY (table1_id) REFERENCES table1_group1_13000025(id) - table2_group1_table1_id_fkey_13000033 | FOREIGN KEY (table1_id) REFERENCES table1_group1_13000027(id) - test_constraint_1230019 | FOREIGN KEY (l_orderkey) REFERENCES tenant_isolation.orders_streaming_1230016(o_orderkey) - test_constraint_1230020 | FOREIGN KEY (l_orderkey) REFERENCES tenant_isolation.orders_streaming_1230017(o_orderkey) - test_constraint_1230021 | FOREIGN KEY (l_orderkey) REFERENCES tenant_isolation.orders_streaming_1230018(o_orderkey) - test_constraint_1230025 | FOREIGN KEY (l_orderkey) REFERENCES tenant_isolation.orders_streaming_1230022(o_orderkey) - test_constraint_1230026 | FOREIGN KEY (l_orderkey) REFERENCES tenant_isolation.orders_streaming_1230023(o_orderkey) - test_constraint_1230027 | FOREIGN KEY (l_orderkey) REFERENCES tenant_isolation.orders_streaming_1230024(o_orderkey) -(11 rows) - -\c - - - :master_port --- test shard copy with foreign constraints --- we expect it to error out because we do not support foreign constraints with replication factor > 1 -SELECT master_copy_shard_placement(13000022, 'localhost', :worker_2_port, 'localhost', :worker_1_port, false); -ERROR: cannot replicate shards with foreign keys --- lets also test that master_move_shard_placement doesn't break serials -CREATE TABLE serial_move_test (key int, other_val serial); -SET citus.shard_replication_factor TO 1; -SELECT create_distributed_table('serial_move_test', 'key'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - --- key 15 goes to shard xxxxx -INSERT INTO serial_move_test (key) VALUES (15) RETURNING *; - key | other_val ---------------------------------------------------------------------- - 15 | 1 -(1 row) - -INSERT INTO serial_move_test (key) VALUES (15) RETURNING *; - key | other_val ---------------------------------------------------------------------- - 15 | 2 -(1 row) - --- confirm the shard id -SELECT * FROM run_command_on_placements('serial_move_test', 'SELECT DISTINCT key FROM %s WHERE key = 15') WHERE result = '15' AND shardid = 13000034; - nodename | nodeport | shardid | success | result ---------------------------------------------------------------------- - localhost | 57637 | 13000034 | t | 15 -(1 row) - -SELECT master_move_shard_placement(13000034, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- confirm the successfull move -SELECT * FROM run_command_on_placements('serial_move_test', 'SELECT DISTINCT key FROM %s WHERE key = 15') WHERE result = '15' AND shardid = 13000034; - nodename | nodeport | shardid | success | result ---------------------------------------------------------------------- - localhost | 57638 | 13000034 | t | 15 -(1 row) - --- finally show that serials work fine afterwards -INSERT INTO serial_move_test (key) VALUES (15) RETURNING *; - key | other_val ---------------------------------------------------------------------- - 15 | 3 -(1 row) - -INSERT INTO serial_move_test (key) VALUES (15) RETURNING *; - key | other_val ---------------------------------------------------------------------- - 15 | 4 -(1 row) - --- lets do some failure testing -CREATE TABLE logical_failure_test (key int); -SET citus.shard_replication_factor TO 1; -SET citus.shard_count TO 4; -SELECT create_distributed_table('logical_failure_test', 'key'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - --- ensure that the shard is created for this user -\c - - - :worker_2_port -\dt logical_failure_test_13000038 - List of relations - Schema | Name | Type | Owner ---------------------------------------------------------------------- - public | logical_failure_test_13000038 | table | postgres -(1 row) - -DROP TABLE logical_failure_test_13000038; --- should fail since the command wouldn't be able to connect to the worker_1 -\c - - - :master_port -SELECT master_move_shard_placement(13000038, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'force_logical'); -ERROR: could not copy table "logical_failure_test_13000038" from "localhost:xxxxx" -CONTEXT: while executing command on localhost:xxxxx -DROP TABLE logical_failure_test; --- lets test the logical replication modes -CREATE TABLE test_with_pkey (key int PRIMARY KEY, value int NOT NULL); -SET citus.shard_replication_factor TO 1; -SET citus.shard_count TO 4; -SELECT create_distributed_table('test_with_pkey', 'key', colocate_with => 'none'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - --- should succeed since there is a replica identity defined -SELECT master_move_shard_placement(13000042, 'localhost', :worker_1_port, 'localhost', :worker_2_port); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- should succeed since we still have a replica identity -ALTER TABLE test_with_pkey REPLICA IDENTITY FULL; -SELECT master_move_shard_placement(13000042, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'auto'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- this time should fail since we don't have replica identity any more -ALTER TABLE test_with_pkey REPLICA IDENTITY NOTHING; -SELECT master_move_shard_placement(13000042, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'auto'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- should succeed since we still have a replica identity -ALTER TABLE test_with_pkey REPLICA IDENTITY USING INDEX test_with_pkey_pkey; -SELECT master_move_shard_placement(13000042, 'localhost', :worker_1_port, 'localhost', :worker_2_port); -ERROR: could not find placement matching "localhost:xxxxx" -HINT: Confirm the placement still exists and try again. --- one final test with shard_transfer_mode auto -CREATE UNIQUE INDEX req_rep_idx ON test_with_pkey(key, value); -ALTER TABLE test_with_pkey REPLICA IDENTITY USING INDEX req_rep_idx; -SELECT master_move_shard_placement(13000042, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'auto'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - -ALTER TABLE test_with_pkey REPLICA IDENTITY NOTHING; -SELECT master_move_shard_placement(13000042, 'localhost', :worker_1_port, 'localhost', :worker_2_port, 'force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- should succeed but not use logical replication -ALTER TABLE test_with_pkey REPLICA IDENTITY NOTHING; -SET client_min_messages TO DEBUG1; -SELECT master_move_shard_placement(13000042, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'block_writes'); -DEBUG: table "test_with_pkey_13000042" does not exist, skipping -DETAIL: NOTICE from localhost:xxxxx - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - -SET client_min_messages TO DEFAULT; --- we don't support multiple shard moves in a single transaction -SELECT - master_move_shard_placement(shardid, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode:='force_logical') -FROM - pg_dist_shard_placement where nodeport = :worker_1_port AND - shardid IN (SELECT shardid FROM pg_dist_shard WHERE logicalrelid = 'test_with_pkey'::regclass); - master_move_shard_placement ---------------------------------------------------------------------- - - -(2 rows) - --- similar test with explicit transaction block -BEGIN; - - SELECT master_move_shard_placement(13000042, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode:='force_logical'); -ERROR: could not find placement matching "localhost:xxxxx" -HINT: Confirm the placement still exists and try again. - SELECT master_move_shard_placement(13000044, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode:='force_logical'); -ERROR: current transaction is aborted, commands ignored until end of transaction block -COMMIT; --- we do support the same with block writes -SELECT - master_move_shard_placement(shardid, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode:='block_writes') -FROM - pg_dist_shard_placement where nodeport = :worker_1_port AND - shardid IN (SELECT shardid FROM pg_dist_shard WHERE logicalrelid = 'test_with_pkey'::regclass); - master_move_shard_placement ---------------------------------------------------------------------- -(0 rows) - --- we should be able to move shard placements after COMMIT/ABORT -BEGIN; - - SELECT master_move_shard_placement(13000043, 'localhost', :worker_2_port, 'localhost', :worker_1_port, shard_transfer_mode:='force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - -COMMIT; -SELECT master_move_shard_placement(13000045, 'localhost', :worker_2_port, 'localhost', :worker_1_port, shard_transfer_mode:='force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - -BEGIN; - - SELECT master_move_shard_placement(13000043, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode:='force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - -ABORT; -SELECT master_move_shard_placement(13000045, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode:='force_logical'); - master_move_shard_placement ---------------------------------------------------------------------- - -(1 row) - --- we should be able to move shard placements of partitioend tables -CREATE SCHEMA move_partitions; -CREATE TABLE move_partitions.events ( - id serial, - t timestamptz default now(), - payload text -) -PARTITION BY RANGE(t); -ERROR: syntax error at or near "PARTITION" -SET citus.shard_count TO 6; -SELECT create_distributed_table('move_partitions.events', 'id', colocate_with := 'none'); -ERROR: relation "move_partitions.events" does not exist -CREATE TABLE move_partitions.events_1 PARTITION OF move_partitions.events -FOR VALUES FROM ('2015-01-01') TO ('2016-01-01'); -ERROR: syntax error at or near "PARTITION" -INSERT INTO move_partitions.events (t, payload) -SELECT '2015-01-01'::date + (interval '1 day' * s), s FROM generate_series(1, 100) s; -ERROR: relation "move_partitions.events" does not exist -SELECT count(*) FROM move_partitions.events; -ERROR: relation "move_partitions.events" does not exist --- try to move automatically -SELECT master_move_shard_placement(shardid, 'localhost', :worker_2_port, 'localhost', :worker_1_port) -FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) -WHERE logicalrelid = 'move_partitions.events'::regclass AND nodeport = :worker_2_port -ORDER BY shardid LIMIT 1; -ERROR: relation "move_partitions.events" does not exist --- force logical replication -SELECT master_move_shard_placement(shardid, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'force_logical') -FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) -WHERE logicalrelid = 'move_partitions.events'::regclass AND nodeport = :worker_2_port -ORDER BY shardid LIMIT 1; -ERROR: relation "move_partitions.events" does not exist -SELECT count(*) FROM move_partitions.events; -ERROR: relation "move_partitions.events" does not exist --- add a primary key to the partition -ALTER TABLE move_partitions.events_1 ADD CONSTRAINT e_1_pk PRIMARY KEY (id); -ERROR: relation "move_partitions.events_1" does not exist --- should be able to move automatically now -SELECT master_move_shard_placement(shardid, 'localhost', :worker_2_port, 'localhost', :worker_1_port) -FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) -WHERE logicalrelid = 'move_partitions.events'::regclass AND nodeport = :worker_2_port -ORDER BY shardid LIMIT 1; -ERROR: relation "move_partitions.events" does not exist -SELECT count(*) FROM move_partitions.events; -ERROR: relation "move_partitions.events" does not exist --- should also be able to move with block writes -SELECT master_move_shard_placement(shardid, 'localhost', :worker_2_port, 'localhost', :worker_1_port, 'block_writes') -FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) -WHERE logicalrelid = 'move_partitions.events'::regclass AND nodeport = :worker_2_port -ORDER BY shardid LIMIT 1; -ERROR: relation "move_partitions.events" does not exist -SELECT count(*) FROM move_partitions.events; -ERROR: relation "move_partitions.events" does not exist --- should have moved all shards to node 1 (2*6 = 12) -SELECT count(*) -FROM pg_dist_shard JOIN pg_dist_shard_placement USING (shardid) -WHERE logicalrelid::text LIKE 'move_partitions.events%' AND nodeport = :worker_1_port; - count ---------------------------------------------------------------------- - 0 -(1 row) - -DROP TABLE move_partitions.events; -ERROR: table "events" does not exist --- set back to the defaults and drop the table -SET client_min_messages TO DEFAULT; -DROP TABLE test_with_pkey; diff --git a/src/test/regress/expected/multi_colocation_utils.out b/src/test/regress/expected/multi_colocation_utils.out index 1fa58264c..6063bc168 100644 --- a/src/test/regress/expected/multi_colocation_utils.out +++ b/src/test/regress/expected/multi_colocation_utils.out @@ -508,18 +508,15 @@ DROP TABLE table2_groupA; SELECT * FROM pg_dist_colocation WHERE colocationid = 4; colocationid | shardcount | replicationfactor | distributioncolumntype | distributioncolumncollation --------------------------------------------------------------------- - 4 | 2 | 2 | 23 | 0 -(1 row) +(0 rows) -- check to see whether metadata is synced SELECT nodeport, unnest(result::jsonb[]) FROM run_command_on_workers($$ -SELECT array_agg(row_to_json(c)) FROM pg_dist_colocation c WHERE colocationid = 4 +SELECT coalesce(array_agg(row_to_json(c)), '{}') FROM pg_dist_colocation c WHERE colocationid = 4 $$); - nodeport | unnest + nodeport | unnest --------------------------------------------------------------------- - 57637 | {"shardcount": 2, "colocationid": 4, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} - 57638 | {"shardcount": 2, "colocationid": 4, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} -(2 rows) +(0 rows) -- create dropped colocation group again SET citus.shard_count = 2; @@ -606,33 +603,35 @@ SELECT * FROM pg_dist_colocation ORDER BY colocationid; colocationid | shardcount | replicationfactor | distributioncolumntype | distributioncolumncollation --------------------------------------------------------------------- - 4 | 2 | 2 | 23 | 0 5 | 2 | 1 | 23 | 0 6 | 2 | 2 | 25 | 100 7 | 8 | 2 | 23 | 0 + 8 | 2 | 2 | 23 | 0 + 9 | 2 | 2 | 23 | 0 + 10 | 2 | 2 | 23 | 0 11 | 3 | 2 | 23 | 0 -(5 rows) +(7 rows) SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE colocationid >= 1 AND colocationid < 1000 ORDER BY colocationid, logicalrelid; logicalrelid | colocationid --------------------------------------------------------------------- - table1_groupe | 4 - table2_groupe | 4 - table3_groupe | 4 - schema_colocation.table4_groupe | 4 - table4_groupe | 4 table1_groupb | 5 table2_groupb | 5 table1_groupc | 6 table2_groupc | 6 table1_groupd | 7 table2_groupd | 7 - table1_group_none_1 | 8 - table2_group_none_1 | 8 - table1_group_none_2 | 9 - table1_group_none_3 | 10 + table1_groupe | 8 + table2_groupe | 8 + table3_groupe | 8 + schema_colocation.table4_groupe | 8 + table4_groupe | 8 + table1_group_none_1 | 9 + table2_group_none_1 | 9 + table1_group_none_2 | 10 + table1_group_none_3 | 11 table1_group_default | 11 (16 rows) @@ -699,12 +698,15 @@ SELECT * FROM pg_dist_colocation ORDER BY colocationid; colocationid | shardcount | replicationfactor | distributioncolumntype | distributioncolumncollation --------------------------------------------------------------------- - 4 | 2 | 2 | 23 | 0 5 | 2 | 1 | 23 | 0 6 | 2 | 2 | 25 | 100 7 | 8 | 2 | 23 | 0 + 8 | 2 | 2 | 23 | 0 + 9 | 2 | 2 | 23 | 0 + 10 | 2 | 2 | 23 | 0 11 | 3 | 2 | 23 | 0 -(5 rows) + 12 | 1 | -1 | 0 | 0 +(8 rows) -- cross check with internal colocation API SELECT @@ -739,8 +741,9 @@ ORDER BY table3_groupe | table4_groupe | t schema_colocation.table4_groupe | table4_groupe | t table1_group_none_1 | table2_group_none_1 | t + table1_group_none_3 | table1_group_default | t table1_groupf | table2_groupf | t -(16 rows) +(17 rows) -- check created shards SELECT @@ -979,7 +982,9 @@ SELECT * FROM pg_dist_colocation 3 | 8 | 2 | 23 | 0 4 | 2 | 2 | 23 | 0 5 | 2 | 2 | 23 | 0 -(5 rows) + 6 | 2 | 2 | 23 | 0 + 7 | 2 | 2 | 23 | 0 +(7 rows) -- check to see whether metadata is synced SELECT nodeport, unnest(result::jsonb[]) FROM run_command_on_workers($$ @@ -993,12 +998,16 @@ $$); 57637 | {"shardcount": 8, "colocationid": 3, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57637 | {"shardcount": 2, "colocationid": 4, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57637 | {"shardcount": 2, "colocationid": 5, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} + 57637 | {"shardcount": 2, "colocationid": 6, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} + 57637 | {"shardcount": 2, "colocationid": 7, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 1, "replicationfactor": 1, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 2, "replicationfactor": 2, "distributioncolumntype": "25", "distributioncolumncollation": "100"} 57638 | {"shardcount": 8, "colocationid": 3, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 4, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 5, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} -(10 rows) + 57638 | {"shardcount": 2, "colocationid": 6, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} + 57638 | {"shardcount": 2, "colocationid": 7, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} +(14 rows) SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE colocationid >= 1 AND colocationid < 1000 @@ -1073,7 +1082,8 @@ SELECT * FROM pg_dist_colocation 3 | 8 | 2 | 23 | 0 4 | 2 | 2 | 23 | 0 5 | 2 | 2 | 23 | 0 -(5 rows) + 7 | 2 | 2 | 23 | 0 +(6 rows) -- check to see whether metadata is synced SELECT nodeport, unnest(result::jsonb[]) FROM run_command_on_workers($$ @@ -1087,12 +1097,14 @@ $$); 57637 | {"shardcount": 8, "colocationid": 3, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57637 | {"shardcount": 2, "colocationid": 4, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57637 | {"shardcount": 2, "colocationid": 5, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} + 57637 | {"shardcount": 2, "colocationid": 7, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 1, "replicationfactor": 1, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 2, "replicationfactor": 2, "distributioncolumntype": "25", "distributioncolumncollation": "100"} 57638 | {"shardcount": 8, "colocationid": 3, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 4, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} 57638 | {"shardcount": 2, "colocationid": 5, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} -(10 rows) + 57638 | {"shardcount": 2, "colocationid": 7, "replicationfactor": 2, "distributioncolumntype": "23", "distributioncolumncollation": "0"} +(12 rows) SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE colocationid >= 1 AND colocationid < 1000 @@ -1443,3 +1455,84 @@ DROP TABLE range_table; DROP TABLE none; DROP TABLE ref; DROP TABLE local_table; +CREATE TABLE tbl_1 (a INT, b INT); +CREATE TABLE tbl_2 (a INT, b INT); +CREATE TABLE tbl_3 (a INT, b INT); +SELECT create_distributed_table('tbl_1', 'a', shard_count:=4); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('tbl_2', 'a', shard_count:=4); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('tbl_3', 'a', shard_count:=4, colocate_with:='NONE'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT colocation_id AS col_id_1 FROM citus_tables WHERE table_name::text = 'tbl_1' \gset +SELECT colocation_id AS col_id_2 FROM citus_tables WHERE table_name::text = 'tbl_2' \gset +SELECT colocation_id AS col_id_3 FROM citus_tables WHERE table_name::text = 'tbl_3' \gset +-- check that tables are colocated correctly +SELECT :col_id_1 = :col_id_2; + ?column? +--------------------------------------------------------------------- + t +(1 row) + +SELECT :col_id_1 = :col_id_3; + ?column? +--------------------------------------------------------------------- + f +(1 row) + +-- check that there are separate rows for both colocation groups in pg_dist_colocation +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_1 +); + result +--------------------------------------------------------------------- + 1 + 1 + 1 +(3 rows) + +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_3 +); + result +--------------------------------------------------------------------- + 1 + 1 + 1 +(3 rows) + +DROP TABLE tbl_1, tbl_3; +-- check that empty colocation group is dropped and non-empty is not +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_1 +); + result +--------------------------------------------------------------------- + 1 + 1 + 1 +(3 rows) + +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_3 +); + result +--------------------------------------------------------------------- + 0 + 0 + 0 +(3 rows) + +DROP TABLE tbl_2; diff --git a/src/test/regress/expected/multi_copy.out b/src/test/regress/expected/multi_copy.out index ea7a58d63..ab8f6ed14 100644 --- a/src/test/regress/expected/multi_copy.out +++ b/src/test/regress/expected/multi_copy.out @@ -650,7 +650,6 @@ INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'packed_numbers_hash'::regclass::oid, 0); INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'super_packed_numbers_hash'::regclass::oid, 0); INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'table_to_distribute'::regclass::oid, 0); -INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'second_dustbunnies'::regclass::oid, 0); SET client_min_messages TO ERROR; SELECT 1 FROM master_activate_node('localhost', :worker_1_port); ?column? diff --git a/src/test/regress/expected/multi_create_table_constraints.out b/src/test/regress/expected/multi_create_table_constraints.out index f68983d6f..f4b9fa889 100644 --- a/src/test/regress/expected/multi_create_table_constraints.out +++ b/src/test/regress/expected/multi_create_table_constraints.out @@ -42,6 +42,24 @@ CREATE TABLE pk_on_non_part_col SELECT create_distributed_table('pk_on_non_part_col', 'partition_col', 'hash'); ERROR: cannot create constraint on "pk_on_non_part_col" DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +-- check that we can disable the constraint check +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +SELECT create_distributed_table('pk_on_non_part_col', 'partition_col', 'hash'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- not enforced across shards +INSERT INTO pk_on_non_part_col VALUES (1,1); +INSERT INTO pk_on_non_part_col VALUES (2,1); +-- enforced within shard +INSERT INTO pk_on_non_part_col VALUES (1,1); +ERROR: duplicate key value violates unique constraint "pk_on_non_part_col_pkey_365000" +DETAIL: Key (other_col)=(1) already exists. +CONTEXT: while executing command on localhost:xxxxx +END; CREATE TABLE uq_on_non_part_col ( partition_col integer, @@ -59,6 +77,24 @@ CREATE TABLE ex_on_non_part_col SELECT create_distributed_table('ex_on_non_part_col', 'partition_col', 'hash'); ERROR: cannot create constraint on "ex_on_non_part_col" DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +-- check that we can disable the constraint check +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +SELECT create_distributed_table('ex_on_non_part_col', 'partition_col', 'hash'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- not enforced across shards +INSERT INTO ex_on_non_part_col VALUES (1,1); +INSERT INTO ex_on_non_part_col VALUES (2,1); +-- enforced within shard +INSERT INTO ex_on_non_part_col VALUES (1,1); +ERROR: conflicting key value violates exclusion constraint "ex_on_non_part_col_other_col_excl_365004" +DETAIL: Key (other_col)=(1) conflicts with existing key (other_col)=(1). +CONTEXT: while executing command on localhost:xxxxx +END; -- now show that Citus can distribute unique and EXCLUDE constraints that -- include the partition column for hash-partitioned tables. -- However, EXCLUDE constraints must include the partition column with @@ -100,9 +136,37 @@ SELECT create_distributed_table('uq_two_columns', 'partition_col', 'hash'); INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); -ERROR: duplicate key value violates unique constraint "uq_two_columns_partition_col_other_col_key_365008" +ERROR: duplicate key value violates unique constraint "uq_two_columns_partition_col_other_col_key_365016" DETAIL: Key (partition_col, other_col)=(1, 1) already exists. CONTEXT: while executing command on localhost:xxxxx +CREATE TABLE pk_on_two_non_part_cols +( + partition_col integer, + other_col integer, + other_col_2 text, + PRIMARY KEY (other_col, other_col_2) +); +SELECT create_distributed_table('pk_on_two_non_part_cols', 'partition_col', 'hash'); +ERROR: cannot create constraint on "pk_on_two_non_part_cols" +DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +-- check that we can disable the constraint check +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +SELECT create_distributed_table('pk_on_two_non_part_cols', 'partition_col', 'hash'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- not enforced across shards +INSERT INTO pk_on_two_non_part_cols VALUES (1,1,1); +INSERT INTO pk_on_two_non_part_cols VALUES (2,1,1); +-- enforced within shard +INSERT INTO pk_on_two_non_part_cols VALUES (1,1,1); +ERROR: duplicate key value violates unique constraint "pk_on_two_non_part_cols_pkey_365020" +DETAIL: Key (other_col, other_col_2)=(1, 1) already exists. +CONTEXT: while executing command on localhost:xxxxx +END; CREATE TABLE ex_on_part_col ( partition_col integer, @@ -117,7 +181,7 @@ SELECT create_distributed_table('ex_on_part_col', 'partition_col', 'hash'); INSERT INTO ex_on_part_col (partition_col, other_col) VALUES (1,1); INSERT INTO ex_on_part_col (partition_col, other_col) VALUES (1,2); -ERROR: conflicting key value violates exclusion constraint "ex_on_part_col_partition_col_excl_365012" +ERROR: conflicting key value violates exclusion constraint "ex_on_part_col_partition_col_excl_365024" DETAIL: Key (partition_col)=(1) conflicts with existing key (partition_col)=(1). CONTEXT: while executing command on localhost:xxxxx CREATE TABLE ex_on_two_columns @@ -134,7 +198,7 @@ SELECT create_distributed_table('ex_on_two_columns', 'partition_col', 'hash'); INSERT INTO ex_on_two_columns (partition_col, other_col) VALUES (1,1); INSERT INTO ex_on_two_columns (partition_col, other_col) VALUES (1,1); -ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_partition_col_other_col_excl_365016" +ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_partition_col_other_col_excl_365028" DETAIL: Key (partition_col, other_col)=(1, 1) conflicts with existing key (partition_col, other_col)=(1, 1). CONTEXT: while executing command on localhost:xxxxx CREATE TABLE ex_on_two_columns_prt @@ -153,7 +217,7 @@ INSERT INTO ex_on_two_columns_prt (partition_col, other_col) VALUES (1,1); INSERT INTO ex_on_two_columns_prt (partition_col, other_col) VALUES (1,1); INSERT INTO ex_on_two_columns_prt (partition_col, other_col) VALUES (1,101); INSERT INTO ex_on_two_columns_prt (partition_col, other_col) VALUES (1,101); -ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_prt_partition_col_other_col_excl_365020" +ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_prt_partition_col_other_col_excl_365032" DETAIL: Key (partition_col, other_col)=(1, 101) conflicts with existing key (partition_col, other_col)=(1, 101). CONTEXT: while executing command on localhost:xxxxx CREATE TABLE ex_wrong_operator @@ -179,7 +243,7 @@ SELECT create_distributed_table('ex_overlaps', 'partition_col', 'hash'); INSERT INTO ex_overlaps (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-01 00:00:00, 2016-02-01 00:00:00]'); INSERT INTO ex_overlaps (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-15 00:00:00, 2016-02-01 00:00:00]'); -ERROR: conflicting key value violates exclusion constraint "ex_overlaps_other_col_partition_col_excl_365027" +ERROR: conflicting key value violates exclusion constraint "ex_overlaps_other_col_partition_col_excl_365039" DETAIL: Key (other_col, partition_col)=(["2016-01-15 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]) conflicts with existing key (other_col, partition_col)=(["2016-01-01 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]). CONTEXT: while executing command on localhost:xxxxx -- now show that Citus can distribute unique and EXCLUDE constraints that @@ -223,7 +287,7 @@ SELECT create_distributed_table('uq_two_columns_named', 'partition_col', 'hash') INSERT INTO uq_two_columns_named (partition_col, other_col) VALUES (1,1); INSERT INTO uq_two_columns_named (partition_col, other_col) VALUES (1,1); -ERROR: duplicate key value violates unique constraint "uq_two_columns_named_uniq_365036" +ERROR: duplicate key value violates unique constraint "uq_two_columns_named_uniq_365048" DETAIL: Key (partition_col, other_col)=(1, 1) already exists. CONTEXT: while executing command on localhost:xxxxx CREATE TABLE ex_on_part_col_named @@ -240,7 +304,7 @@ SELECT create_distributed_table('ex_on_part_col_named', 'partition_col', 'hash') INSERT INTO ex_on_part_col_named (partition_col, other_col) VALUES (1,1); INSERT INTO ex_on_part_col_named (partition_col, other_col) VALUES (1,2); -ERROR: conflicting key value violates exclusion constraint "ex_on_part_col_named_exclude_365040" +ERROR: conflicting key value violates exclusion constraint "ex_on_part_col_named_exclude_365052" DETAIL: Key (partition_col)=(1) conflicts with existing key (partition_col)=(1). CONTEXT: while executing command on localhost:xxxxx CREATE TABLE ex_on_two_columns_named @@ -257,7 +321,7 @@ SELECT create_distributed_table('ex_on_two_columns_named', 'partition_col', 'has INSERT INTO ex_on_two_columns_named (partition_col, other_col) VALUES (1,1); INSERT INTO ex_on_two_columns_named (partition_col, other_col) VALUES (1,1); -ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_named_exclude_365044" +ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_named_exclude_365056" DETAIL: Key (partition_col, other_col)=(1, 1) conflicts with existing key (partition_col, other_col)=(1, 1). CONTEXT: while executing command on localhost:xxxxx CREATE TABLE ex_multiple_excludes @@ -276,11 +340,11 @@ SELECT create_distributed_table('ex_multiple_excludes', 'partition_col', 'hash') INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,1,1); INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,1,2); -ERROR: conflicting key value violates exclusion constraint "ex_multiple_excludes_excl1_365048" +ERROR: conflicting key value violates exclusion constraint "ex_multiple_excludes_excl1_365060" DETAIL: Key (partition_col, other_col)=(1, 1) conflicts with existing key (partition_col, other_col)=(1, 1). CONTEXT: while executing command on localhost:xxxxx INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,2,1); -ERROR: conflicting key value violates exclusion constraint "ex_multiple_excludes_excl2_365048" +ERROR: conflicting key value violates exclusion constraint "ex_multiple_excludes_excl2_365060" DETAIL: Key (partition_col, other_other_col)=(1, 1) conflicts with existing key (partition_col, other_other_col)=(1, 1). CONTEXT: while executing command on localhost:xxxxx CREATE TABLE ex_wrong_operator_named @@ -306,7 +370,7 @@ SELECT create_distributed_table('ex_overlaps_named', 'partition_col', 'hash'); INSERT INTO ex_overlaps_named (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-01 00:00:00, 2016-02-01 00:00:00]'); INSERT INTO ex_overlaps_named (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-15 00:00:00, 2016-02-01 00:00:00]'); -ERROR: conflicting key value violates exclusion constraint "ex_overlaps_operator_named_exclude_365055" +ERROR: conflicting key value violates exclusion constraint "ex_overlaps_operator_named_exclude_365067" DETAIL: Key (other_col, partition_col)=(["2016-01-15 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]) conflicts with existing key (other_col, partition_col)=(["2016-01-01 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]). CONTEXT: while executing command on localhost:xxxxx -- now show that Citus allows unique constraints on range-partitioned tables. @@ -336,13 +400,13 @@ SELECT create_distributed_table('check_example', 'partition_col', 'hash'); \c - - :public_worker_1_host :worker_1_port SELECT "Column", "Type", "Definition" FROM index_attrs WHERE - relid = 'check_example_partition_col_key_365056'::regclass; + relid = 'check_example_partition_col_key_365068'::regclass; Column | Type | Definition --------------------------------------------------------------------- partition_col | integer | partition_col (1 row) -SELECT "Constraint", "Definition" FROM table_checks WHERE relid='public.check_example_365056'::regclass; +SELECT "Constraint", "Definition" FROM table_checks WHERE relid='public.check_example_365068'::regclass; Constraint | Definition --------------------------------------------------------------------- check_example_other_col_check | CHECK (other_col >= 100) diff --git a/src/test/regress/expected/multi_deparse_shard_query.out b/src/test/regress/expected/multi_deparse_shard_query.out index 32a97eafa..b24c0e4cb 100644 --- a/src/test/regress/expected/multi_deparse_shard_query.out +++ b/src/test/regress/expected/multi_deparse_shard_query.out @@ -1,6 +1,17 @@ -- -- MULTI_DEPARSE_SHARD_QUERY -- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + SET citus.next_shard_id TO 13100000; SET citus.shard_replication_factor TO 1; CREATE FUNCTION deparse_shard_query_test(text) @@ -63,7 +74,7 @@ SELECT deparse_shard_query_test(' INSERT INTO raw_events_1 SELECT * FROM raw_events_1; '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_1, value_2, value_3, value_4, value_5, value_6, value_7, event_at) SELECT tenant_id, value_1, value_2, value_3, value_4, value_5, value_6, value_7, event_at FROM public.raw_events_1 +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_1, value_2, value_3, value_4, value_5, value_6, value_7, event_at) SELECT raw_events_1_1.tenant_id, raw_events_1_1.value_1, raw_events_1_1.value_2, raw_events_1_1.value_3, raw_events_1_1.value_4, raw_events_1_1.value_5, raw_events_1_1.value_6, raw_events_1_1.value_7, raw_events_1_1.event_at FROM public.raw_events_1 raw_events_1_1 deparse_shard_query_test --------------------------------------------------------------------- @@ -76,7 +87,7 @@ SELECT FROM raw_events_1; '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, event_at) SELECT tenant_id, value_4, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, event_at) SELECT raw_events_1_1.tenant_id, raw_events_1_1.value_4, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 raw_events_1_1 deparse_shard_query_test --------------------------------------------------------------------- @@ -90,7 +101,7 @@ SELECT FROM raw_events_1; '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_2, value_4, value_5, value_6, event_at) SELECT tenant_id, (value_5)::integer AS value_5, value_4, (value_2)::text AS value_2, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_2, value_4, value_5, value_6, event_at) SELECT raw_events_1_1.tenant_id, (raw_events_1_1.value_5)::integer AS value_5, raw_events_1_1.value_4, (raw_events_1_1.value_2)::text AS value_2, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 raw_events_1_1 deparse_shard_query_test --------------------------------------------------------------------- @@ -104,7 +115,7 @@ SELECT FROM raw_events_2; '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_2, value_4, value_5, value_6, event_at) SELECT tenant_id, (value_5)::integer AS value_5, value_4, (value_2)::text AS value_2, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_2 +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_2, value_4, value_5, value_6, event_at) SELECT raw_events_2.tenant_id, (raw_events_2.value_5)::integer AS value_5, raw_events_2.value_4, (raw_events_2.value_2)::text AS value_2, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_2 deparse_shard_query_test --------------------------------------------------------------------- @@ -120,7 +131,7 @@ FROM GROUP BY tenant_id, date_trunc(\'hour\', event_at) '); -INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, average_value_3, sum_value_4, average_value_6, rollup_hour) SELECT tenant_id, sum(value_1) AS sum, avg(value_3) AS avg, sum(value_4) AS sum, avg(value_6) AS avg, date_trunc('hour'::text, (event_at)::timestamp with time zone) AS date_trunc FROM public.raw_events_1 GROUP BY tenant_id, (date_trunc('hour'::text, (event_at)::timestamp with time zone)) +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, average_value_3, sum_value_4, average_value_6, rollup_hour) SELECT raw_events_1.tenant_id, sum(raw_events_1.value_1) AS sum, avg(raw_events_1.value_3) AS avg, sum(raw_events_1.value_4) AS sum, avg(raw_events_1.value_6) AS avg, date_trunc('hour'::text, (raw_events_1.event_at)::timestamp with time zone) AS date_trunc FROM public.raw_events_1 GROUP BY raw_events_1.tenant_id, (date_trunc('hour'::text, (raw_events_1.event_at)::timestamp with time zone)) deparse_shard_query_test --------------------------------------------------------------------- @@ -137,7 +148,7 @@ FROM WHERE raw_events_1.tenant_id = raw_events_2.tenant_id; '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_3, value_6, event_at) SELECT raw_events_1.tenant_id, raw_events_2.value_3, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1, public.raw_events_2 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id) +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_3, value_6, event_at) SELECT raw_events_1_1.tenant_id, raw_events_2.value_3, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 raw_events_1_1, public.raw_events_2 WHERE (raw_events_1_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id) deparse_shard_query_test --------------------------------------------------------------------- @@ -153,7 +164,7 @@ FROM WHERE raw_events_1.tenant_id = raw_events_2.tenant_id GROUP BY raw_events_1.event_at '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_3, value_6, event_at) SELECT avg(raw_events_1.value_3) AS avg, max(raw_events_2.value_3) AS max, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1, public.raw_events_2 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id) GROUP BY raw_events_1.event_at +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_3, value_6, event_at) SELECT avg(raw_events_1_1.value_3) AS avg, max(raw_events_2.value_3) AS max, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 raw_events_1_1, public.raw_events_2 WHERE (raw_events_1_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id) GROUP BY raw_events_1_1.event_at deparse_shard_query_test --------------------------------------------------------------------- @@ -190,7 +201,7 @@ FROM GROUP BY event_at, tenant_id; '); -INFO: query: WITH first_tenant AS (SELECT raw_events_1.event_at, raw_events_1.value_5, raw_events_1.tenant_id FROM public.raw_events_1) INSERT INTO public.aggregated_events (tenant_id, sum_value_5, rollup_hour) SELECT tenant_id, sum((value_5)::integer) AS sum, event_at FROM public.raw_events_1 GROUP BY event_at, tenant_id +INFO: query: WITH first_tenant AS (SELECT raw_events_1.event_at, raw_events_1.value_5, raw_events_1.tenant_id FROM public.raw_events_1) INSERT INTO public.aggregated_events (tenant_id, sum_value_5, rollup_hour) SELECT raw_events_1.tenant_id, sum((raw_events_1.value_5)::integer) AS sum, raw_events_1.event_at FROM public.raw_events_1 GROUP BY raw_events_1.event_at, raw_events_1.tenant_id deparse_shard_query_test --------------------------------------------------------------------- @@ -206,7 +217,7 @@ FROM GROUP BY event_at, tenant_id; '); -INFO: query: WITH first_tenant AS (SELECT raw_events_1.event_at, raw_events_1.value_5, raw_events_1.tenant_id FROM public.raw_events_1) INSERT INTO public.aggregated_events (tenant_id, sum_value_5) SELECT tenant_id, sum((value_5)::integer) AS sum FROM public.raw_events_1 GROUP BY event_at, tenant_id +INFO: query: WITH first_tenant AS (SELECT raw_events_1.event_at, raw_events_1.value_5, raw_events_1.tenant_id FROM public.raw_events_1) INSERT INTO public.aggregated_events (tenant_id, sum_value_5) SELECT raw_events_1.tenant_id, sum((raw_events_1.value_5)::integer) AS sum FROM public.raw_events_1 GROUP BY raw_events_1.event_at, raw_events_1.tenant_id deparse_shard_query_test --------------------------------------------------------------------- @@ -225,7 +236,7 @@ WITH RECURSIVE hierarchy as ( h.value_1 = re.value_6)) SELECT * FROM hierarchy WHERE LEVEL <= 2; '); -INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) WITH RECURSIVE hierarchy AS (SELECT raw_events_1.value_1, 1 AS level, raw_events_1.tenant_id FROM public.raw_events_1 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) 1) UNION SELECT re.value_2, (h.level OPERATOR(pg_catalog.+) 1), re.tenant_id FROM (hierarchy h JOIN public.raw_events_1 re ON (((h.tenant_id OPERATOR(pg_catalog.=) re.tenant_id) AND (h.value_1 OPERATOR(pg_catalog.=) re.value_6))))) SELECT tenant_id, value_1, level FROM hierarchy WHERE (level OPERATOR(pg_catalog.<=) 2) +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) WITH RECURSIVE hierarchy AS (SELECT raw_events_1.value_1, 1 AS level, raw_events_1.tenant_id FROM public.raw_events_1 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) 1) UNION SELECT re.value_2, (h.level OPERATOR(pg_catalog.+) 1), re.tenant_id FROM (hierarchy h JOIN public.raw_events_1 re ON (((h.tenant_id OPERATOR(pg_catalog.=) re.tenant_id) AND (h.value_1 OPERATOR(pg_catalog.=) re.value_6))))) SELECT hierarchy.tenant_id, hierarchy.value_1, hierarchy.level FROM hierarchy WHERE (hierarchy.level OPERATOR(pg_catalog.<=) 2) deparse_shard_query_test --------------------------------------------------------------------- @@ -238,7 +249,7 @@ SELECT FROM raw_events_1; '); -INFO: query: INSERT INTO public.aggregated_events (sum_value_1) SELECT DISTINCT value_1 FROM public.raw_events_1 +INFO: query: INSERT INTO public.aggregated_events (sum_value_1) SELECT DISTINCT raw_events_1.value_1 FROM public.raw_events_1 deparse_shard_query_test --------------------------------------------------------------------- @@ -251,7 +262,7 @@ SELECT value_3, value_2, tenant_id FROM raw_events_1 WHERE (value_5 like \'%s\' or value_5 like \'%a\') and (tenant_id = 1) and (value_6 < 3000 or value_3 > 8000); '); -INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) SELECT tenant_id, value_2, value_3 FROM public.raw_events_1 WHERE (((value_5 OPERATOR(pg_catalog.~~) '%s'::text) OR (value_5 OPERATOR(pg_catalog.~~) '%a'::text)) AND (tenant_id OPERATOR(pg_catalog.=) 1) AND ((value_6 OPERATOR(pg_catalog.<) 3000) OR (value_3 OPERATOR(pg_catalog.>) (8000)::double precision))) +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) SELECT raw_events_1.tenant_id, raw_events_1.value_2, raw_events_1.value_3 FROM public.raw_events_1 WHERE (((raw_events_1.value_5 OPERATOR(pg_catalog.~~) '%s'::text) OR (raw_events_1.value_5 OPERATOR(pg_catalog.~~) '%a'::text)) AND (raw_events_1.tenant_id OPERATOR(pg_catalog.=) 1) AND ((raw_events_1.value_6 OPERATOR(pg_catalog.<) 3000) OR (raw_events_1.value_3 OPERATOR(pg_catalog.>) (8000)::double precision))) deparse_shard_query_test --------------------------------------------------------------------- @@ -263,7 +274,7 @@ SELECT rank() OVER (PARTITION BY tenant_id ORDER BY value_6), tenant_id FROM raw_events_1 WHERE event_at = now(); '); -INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_5) SELECT tenant_id, rank() OVER (PARTITION BY tenant_id ORDER BY value_6) AS rank FROM public.raw_events_1 WHERE (event_at OPERATOR(pg_catalog.=) now()) +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_5) SELECT raw_events_1.tenant_id, rank() OVER (PARTITION BY raw_events_1.tenant_id ORDER BY raw_events_1.value_6) AS rank FROM public.raw_events_1 WHERE (raw_events_1.event_at OPERATOR(pg_catalog.=) now()) deparse_shard_query_test --------------------------------------------------------------------- @@ -276,7 +287,7 @@ SELECT random(), int4eq(1, max(value_1))::int, value_6 WHERE event_at = now() GROUP BY event_at, value_7, value_6; '); -INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_4, sum_value_5) SELECT (int4eq(1, max(value_1)))::integer AS int4eq, value_6, random() AS random FROM public.raw_events_1 WHERE (event_at OPERATOR(pg_catalog.=) now()) GROUP BY event_at, value_7, value_6 +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_4, sum_value_5) SELECT (int4eq(1, max(raw_events_1.value_1)))::integer AS int4eq, raw_events_1.value_6, random() AS random FROM public.raw_events_1 WHERE (raw_events_1.event_at OPERATOR(pg_catalog.=) now()) GROUP BY raw_events_1.event_at, raw_events_1.value_7, raw_events_1.value_6 deparse_shard_query_test --------------------------------------------------------------------- @@ -297,7 +308,7 @@ SELECT FROM raw_events_1; '); -INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1) SELECT max(tenant_id) AS max, count(DISTINCT CASE WHEN (value_1 OPERATOR(pg_catalog.>) 100) THEN tenant_id ELSE (value_6)::bigint END) AS c FROM public.raw_events_1 +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1) SELECT max(raw_events_1.tenant_id) AS max, count(DISTINCT CASE WHEN (raw_events_1.value_1 OPERATOR(pg_catalog.>) 100) THEN raw_events_1.tenant_id ELSE (raw_events_1.value_6)::bigint END) AS c FROM public.raw_events_1 deparse_shard_query_test --------------------------------------------------------------------- @@ -314,7 +325,7 @@ FROM raw_events_2 ) as foo '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_1, value_6, value_7, event_at) SELECT tenant_id, value_1, 10 AS value_6, value_7, (now())::date AS event_at FROM (SELECT raw_events_2.tenant_id, raw_events_2.value_2 AS value_7, raw_events_2.value_1 FROM public.raw_events_2) foo +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_1, value_6, value_7, event_at) SELECT foo.tenant_id, foo.value_1, 10 AS value_6, foo.value_7, (now())::date AS event_at FROM (SELECT raw_events_2.tenant_id, raw_events_2.value_2 AS value_7, raw_events_2.value_1 FROM public.raw_events_2) foo deparse_shard_query_test --------------------------------------------------------------------- @@ -330,12 +341,12 @@ FROM FROM raw_events_2, raw_events_1 WHERE - raw_events_1.tenant_id = raw_events_2.tenant_id + raw_events_1.tenant_id = raw_events_2.tenant_id ) as foo GROUP BY tenant_id, date_trunc(\'hour\', event_at) '); -INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) SELECT tenant_id, sum(value_1) AS sum, sum((value_5)::bigint) AS sum FROM (SELECT raw_events_1.event_at, raw_events_2.tenant_id, raw_events_2.value_5, raw_events_1.value_1 FROM public.raw_events_2, public.raw_events_1 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id)) foo GROUP BY tenant_id, (date_trunc('hour'::text, (event_at)::timestamp with time zone)) +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) SELECT foo.tenant_id, sum(foo.value_1) AS sum, sum((foo.value_5)::bigint) AS sum FROM (SELECT raw_events_1.event_at, raw_events_2.tenant_id, raw_events_2.value_5, raw_events_1.value_1 FROM public.raw_events_2, public.raw_events_1 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id)) foo GROUP BY foo.tenant_id, (date_trunc('hour'::text, (foo.event_at)::timestamp with time zone)) deparse_shard_query_test --------------------------------------------------------------------- @@ -352,7 +363,7 @@ FROM raw_events_1 ) as foo '); -INFO: query: INSERT INTO public.raw_events_2 (tenant_id, value_1, value_2, value_3, value_4, value_6, event_at) SELECT tenant_id, value_1, value_2, value_3, value_4, (random() OPERATOR(pg_catalog.*) (100)::double precision) AS value_6, (now())::date AS event_at FROM (SELECT raw_events_1.value_2, raw_events_1.value_4, raw_events_1.tenant_id, raw_events_1.value_1, raw_events_1.value_3 FROM public.raw_events_1) foo +INFO: query: INSERT INTO public.raw_events_2 (tenant_id, value_1, value_2, value_3, value_4, value_6, event_at) SELECT foo.tenant_id, foo.value_1, foo.value_2, foo.value_3, foo.value_4, (random() OPERATOR(pg_catalog.*) (100)::double precision) AS value_6, (now())::date AS event_at FROM (SELECT raw_events_1.value_2, raw_events_1.value_4, raw_events_1.tenant_id, raw_events_1.value_1, raw_events_1.value_3 FROM public.raw_events_1) foo deparse_shard_query_test --------------------------------------------------------------------- @@ -369,7 +380,7 @@ FROM raw_events_1 ) as foo '); -INFO: query: INSERT INTO public.raw_events_2 (tenant_id, value_1, value_2, value_3, value_4, value_6, event_at) SELECT value_2, value_4, value_1, value_3, tenant_id, (random() OPERATOR(pg_catalog.*) (100)::double precision) AS value_6, (now())::date AS event_at FROM (SELECT raw_events_1.value_2, raw_events_1.value_4, raw_events_1.tenant_id, raw_events_1.value_1, raw_events_1.value_3 FROM public.raw_events_1) foo +INFO: query: INSERT INTO public.raw_events_2 (tenant_id, value_1, value_2, value_3, value_4, value_6, event_at) SELECT foo.value_2, foo.value_4, foo.value_1, foo.value_3, foo.tenant_id, (random() OPERATOR(pg_catalog.*) (100)::double precision) AS value_6, (now())::date AS event_at FROM (SELECT raw_events_1.value_2, raw_events_1.value_4, raw_events_1.tenant_id, raw_events_1.value_1, raw_events_1.value_3 FROM public.raw_events_1) foo deparse_shard_query_test --------------------------------------------------------------------- @@ -385,7 +396,7 @@ FROM ORDER BY value_2, value_1; '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, value_7, event_at) SELECT tenant_id, value_7, 10 AS value_6, value_7, (now())::date AS event_at FROM public.raw_events_1 ORDER BY value_2, value_1 +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, value_7, event_at) SELECT raw_events_1_1.tenant_id, raw_events_1_1.value_7, 10 AS value_6, raw_events_1_1.value_7, (now())::date AS event_at FROM public.raw_events_1 raw_events_1_1 ORDER BY raw_events_1_1.value_2, raw_events_1_1.value_1 deparse_shard_query_test --------------------------------------------------------------------- @@ -400,7 +411,7 @@ SELECT FROM raw_events_1; '); -INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, value_7, event_at) SELECT tenant_id, value_4, 10 AS value_6, value_7, (now())::date AS event_at FROM public.raw_events_1 +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, value_7, event_at) SELECT raw_events_1_1.tenant_id, raw_events_1_1.value_4, 10 AS value_6, raw_events_1_1.value_7, (now())::date AS event_at FROM public.raw_events_1 raw_events_1_1 deparse_shard_query_test --------------------------------------------------------------------- diff --git a/src/test/regress/expected/multi_deparse_shard_query_0.out b/src/test/regress/expected/multi_deparse_shard_query_0.out new file mode 100644 index 000000000..71742c589 --- /dev/null +++ b/src/test/regress/expected/multi_deparse_shard_query_0.out @@ -0,0 +1,419 @@ +-- +-- MULTI_DEPARSE_SHARD_QUERY +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +SET citus.next_shard_id TO 13100000; +SET citus.shard_replication_factor TO 1; +CREATE FUNCTION deparse_shard_query_test(text) + RETURNS VOID + AS 'citus' + LANGUAGE C STRICT; +-- create the first table +CREATE TABLE raw_events_1 + (tenant_id bigint, + value_1 int, + value_2 int, + value_3 float, + value_4 bigint, + value_5 text, + value_6 int DEfAULT 10, + value_7 int, + event_at date DEfAULT now() + ); +SELECT create_distributed_table('raw_events_1', 'tenant_id', 'hash'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- create the first table +CREATE TABLE raw_events_2 + (tenant_id bigint, + value_1 int, + value_2 int, + value_3 float, + value_4 bigint, + value_5 text, + value_6 float DEfAULT (random()*100)::float, + value_7 int, + event_at date DEfAULT now() + ); +SELECT create_distributed_table('raw_events_2', 'tenant_id', 'hash'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE aggregated_events + (tenant_id bigint, + sum_value_1 bigint, + average_value_2 float, + average_value_3 float, + sum_value_4 bigint, + sum_value_5 float, + average_value_6 int, + rollup_hour date); +SELECT create_distributed_table('aggregated_events', 'tenant_id', 'hash'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- start with very simple examples on a single table +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1 +SELECT * FROM raw_events_1; +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_1, value_2, value_3, value_4, value_5, value_6, value_7, event_at) SELECT tenant_id, value_1, value_2, value_3, value_4, value_5, value_6, value_7, event_at FROM public.raw_events_1 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1(tenant_id, value_4) +SELECT + tenant_id, value_4 +FROM + raw_events_1; +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, event_at) SELECT tenant_id, value_4, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- now that shuffle columns a bit on a single table +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1(value_5, value_2, tenant_id, value_4) +SELECT + value_2::text, value_5::int, tenant_id, value_4 +FROM + raw_events_1; +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_2, value_4, value_5, value_6, event_at) SELECT tenant_id, (value_5)::integer AS value_5, value_4, (value_2)::text AS value_2, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- same test on two different tables +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1(value_5, value_2, tenant_id, value_4) +SELECT + value_2::text, value_5::int, tenant_id, value_4 +FROM + raw_events_2; +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_2, value_4, value_5, value_6, event_at) SELECT tenant_id, (value_5)::integer AS value_5, value_4, (value_2)::text AS value_2, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_2 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- lets do some simple aggregations +SELECT deparse_shard_query_test(E' +INSERT INTO aggregated_events (tenant_id, rollup_hour, sum_value_1, average_value_3, average_value_6, sum_value_4) +SELECT + tenant_id, date_trunc(\'hour\', event_at) , sum(value_1), avg(value_3), avg(value_6), sum(value_4) +FROM + raw_events_1 +GROUP BY + tenant_id, date_trunc(\'hour\', event_at) +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, average_value_3, sum_value_4, average_value_6, rollup_hour) SELECT tenant_id, sum(value_1) AS sum, avg(value_3) AS avg, sum(value_4) AS sum, avg(value_6) AS avg, date_trunc('hour'::text, (event_at)::timestamp with time zone) AS date_trunc FROM public.raw_events_1 GROUP BY tenant_id, (date_trunc('hour'::text, (event_at)::timestamp with time zone)) + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- also some subqueries, JOINS with a complicated target lists +-- a simple JOIN +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1 (value_3, tenant_id) +SELECT + raw_events_2.value_3, raw_events_1.tenant_id +FROM + raw_events_1, raw_events_2 +WHERE + raw_events_1.tenant_id = raw_events_2.tenant_id; +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_3, value_6, event_at) SELECT raw_events_1.tenant_id, raw_events_2.value_3, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1, public.raw_events_2 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id) + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- join with group by +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1 (value_3, tenant_id) +SELECT + max(raw_events_2.value_3), avg(raw_events_1.value_3) +FROM + raw_events_1, raw_events_2 +WHERE + raw_events_1.tenant_id = raw_events_2.tenant_id GROUP BY raw_events_1.event_at +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_3, value_6, event_at) SELECT avg(raw_events_1.value_3) AS avg, max(raw_events_2.value_3) AS max, 10 AS value_6, (now())::date AS event_at FROM public.raw_events_1, public.raw_events_2 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id) GROUP BY raw_events_1.event_at + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- a more complicated JOIN +SELECT deparse_shard_query_test(' +INSERT INTO aggregated_events (sum_value_4, tenant_id) +SELECT + max(r1.value_4), r3.tenant_id +FROM + raw_events_1 r1, raw_events_2 r2, raw_events_1 r3 +WHERE + r1.tenant_id = r2.tenant_id AND r2.tenant_id = r3.tenant_id +GROUP BY + r1.value_1, r3.tenant_id, r2.event_at +ORDER BY + r2.event_at DESC; +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_4) SELECT r3.tenant_id, max(r1.value_4) AS max FROM public.raw_events_1 r1, public.raw_events_2 r2, public.raw_events_1 r3 WHERE ((r1.tenant_id OPERATOR(pg_catalog.=) r2.tenant_id) AND (r2.tenant_id OPERATOR(pg_catalog.=) r3.tenant_id)) GROUP BY r1.value_1, r3.tenant_id, r2.event_at ORDER BY r2.event_at DESC + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- queries with CTEs are supported +SELECT deparse_shard_query_test(' +WITH first_tenant AS (SELECT event_at, value_5, tenant_id FROM raw_events_1) +INSERT INTO aggregated_events (rollup_hour, sum_value_5, tenant_id) +SELECT + event_at, sum(value_5::int), tenant_id +FROM + raw_events_1 +GROUP BY + event_at, tenant_id; +'); +INFO: query: WITH first_tenant AS (SELECT raw_events_1.event_at, raw_events_1.value_5, raw_events_1.tenant_id FROM public.raw_events_1) INSERT INTO public.aggregated_events (tenant_id, sum_value_5, rollup_hour) SELECT tenant_id, sum((value_5)::integer) AS sum, event_at FROM public.raw_events_1 GROUP BY event_at, tenant_id + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(' +WITH first_tenant AS (SELECT event_at, value_5, tenant_id FROM raw_events_1) +INSERT INTO aggregated_events (sum_value_5, tenant_id) +SELECT + sum(value_5::int), tenant_id +FROM + raw_events_1 +GROUP BY + event_at, tenant_id; +'); +INFO: query: WITH first_tenant AS (SELECT raw_events_1.event_at, raw_events_1.value_5, raw_events_1.tenant_id FROM public.raw_events_1) INSERT INTO public.aggregated_events (tenant_id, sum_value_5) SELECT tenant_id, sum((value_5)::integer) AS sum FROM public.raw_events_1 GROUP BY event_at, tenant_id + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(' +INSERT INTO aggregated_events (sum_value_1, sum_value_5, tenant_id) +WITH RECURSIVE hierarchy as ( + SELECT value_1, 1 AS LEVEL, tenant_id + FROM raw_events_1 + WHERE tenant_id = 1 + UNION + SELECT re.value_2, (h.level+1), re.tenant_id + FROM hierarchy h JOIN raw_events_1 re + ON (h.tenant_id = re.tenant_id AND + h.value_1 = re.value_6)) +SELECT * FROM hierarchy WHERE LEVEL <= 2; +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) WITH RECURSIVE hierarchy AS (SELECT raw_events_1.value_1, 1 AS level, raw_events_1.tenant_id FROM public.raw_events_1 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) 1) UNION SELECT re.value_2, (h.level OPERATOR(pg_catalog.+) 1), re.tenant_id FROM (hierarchy h JOIN public.raw_events_1 re ON (((h.tenant_id OPERATOR(pg_catalog.=) re.tenant_id) AND (h.value_1 OPERATOR(pg_catalog.=) re.value_6))))) SELECT tenant_id, value_1, level FROM hierarchy WHERE (level OPERATOR(pg_catalog.<=) 2) + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(' +INSERT INTO aggregated_events (sum_value_1) +SELECT + DISTINCT value_1 +FROM + raw_events_1; +'); +INFO: query: INSERT INTO public.aggregated_events (sum_value_1) SELECT DISTINCT value_1 FROM public.raw_events_1 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- many filters suffled +SELECT deparse_shard_query_test(E' +INSERT INTO aggregated_events (sum_value_5, sum_value_1, tenant_id) +SELECT value_3, value_2, tenant_id + FROM raw_events_1 + WHERE (value_5 like \'%s\' or value_5 like \'%a\') and (tenant_id = 1) and (value_6 < 3000 or value_3 > 8000); +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) SELECT tenant_id, value_2, value_3 FROM public.raw_events_1 WHERE (((value_5 OPERATOR(pg_catalog.~~) '%s'::text) OR (value_5 OPERATOR(pg_catalog.~~) '%a'::text)) AND (tenant_id OPERATOR(pg_catalog.=) 1) AND ((value_6 OPERATOR(pg_catalog.<) 3000) OR (value_3 OPERATOR(pg_catalog.>) (8000)::double precision))) + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(E' +INSERT INTO aggregated_events (sum_value_5, tenant_id) +SELECT rank() OVER (PARTITION BY tenant_id ORDER BY value_6), tenant_id + FROM raw_events_1 + WHERE event_at = now(); +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_5) SELECT tenant_id, rank() OVER (PARTITION BY tenant_id ORDER BY value_6) AS rank FROM public.raw_events_1 WHERE (event_at OPERATOR(pg_catalog.=) now()) + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(E' +INSERT INTO aggregated_events (sum_value_5, tenant_id, sum_value_4) +SELECT random(), int4eq(1, max(value_1))::int, value_6 + FROM raw_events_1 + WHERE event_at = now() + GROUP BY event_at, value_7, value_6; +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_4, sum_value_5) SELECT (int4eq(1, max(value_1)))::integer AS int4eq, value_6, random() AS random FROM public.raw_events_1 WHERE (event_at OPERATOR(pg_catalog.=) now()) GROUP BY event_at, value_7, value_6 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(' +INSERT INTO aggregated_events (sum_value_1, tenant_id) +SELECT + count(DISTINCT CASE + WHEN + value_1 > 100 + THEN + tenant_id + ELSE + value_6 + END) as c, + max(tenant_id) + FROM + raw_events_1; +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1) SELECT max(tenant_id) AS max, count(DISTINCT CASE WHEN (value_1 OPERATOR(pg_catalog.>) 100) THEN tenant_id ELSE (value_6)::bigint END) AS c FROM public.raw_events_1 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1(value_7, value_1, tenant_id) +SELECT + value_7, value_1, tenant_id +FROM + (SELECT + tenant_id, value_2 as value_7, value_1 + FROM + raw_events_2 + ) as foo +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_1, value_6, value_7, event_at) SELECT tenant_id, value_1, 10 AS value_6, value_7, (now())::date AS event_at FROM (SELECT raw_events_2.tenant_id, raw_events_2.value_2 AS value_7, raw_events_2.value_1 FROM public.raw_events_2) foo + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(E' +INSERT INTO aggregated_events(sum_value_1, tenant_id, sum_value_5) +SELECT + sum(value_1), tenant_id, sum(value_5::bigint) +FROM + (SELECT + raw_events_1.event_at, raw_events_2.tenant_id, raw_events_2.value_5, raw_events_1.value_1 + FROM + raw_events_2, raw_events_1 + WHERE + raw_events_1.tenant_id = raw_events_2.tenant_id + ) as foo +GROUP BY + tenant_id, date_trunc(\'hour\', event_at) +'); +INFO: query: INSERT INTO public.aggregated_events (tenant_id, sum_value_1, sum_value_5) SELECT tenant_id, sum(value_1) AS sum, sum((value_5)::bigint) AS sum FROM (SELECT raw_events_1.event_at, raw_events_2.tenant_id, raw_events_2.value_5, raw_events_1.value_1 FROM public.raw_events_2, public.raw_events_1 WHERE (raw_events_1.tenant_id OPERATOR(pg_catalog.=) raw_events_2.tenant_id)) foo GROUP BY tenant_id, (date_trunc('hour'::text, (event_at)::timestamp with time zone)) + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(E' +INSERT INTO raw_events_2(tenant_id, value_1, value_2, value_3, value_4) +SELECT + tenant_id, value_1, value_2, value_3, value_4 +FROM + (SELECT + value_2, value_4, tenant_id, value_1, value_3 + FROM + raw_events_1 + ) as foo +'); +INFO: query: INSERT INTO public.raw_events_2 (tenant_id, value_1, value_2, value_3, value_4, value_6, event_at) SELECT tenant_id, value_1, value_2, value_3, value_4, (random() OPERATOR(pg_catalog.*) (100)::double precision) AS value_6, (now())::date AS event_at FROM (SELECT raw_events_1.value_2, raw_events_1.value_4, raw_events_1.tenant_id, raw_events_1.value_1, raw_events_1.value_3 FROM public.raw_events_1) foo + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +SELECT deparse_shard_query_test(E' +INSERT INTO raw_events_2(tenant_id, value_1, value_4, value_2, value_3) +SELECT + * +FROM + (SELECT + value_2, value_4, tenant_id, value_1, value_3 + FROM + raw_events_1 + ) as foo +'); +INFO: query: INSERT INTO public.raw_events_2 (tenant_id, value_1, value_2, value_3, value_4, value_6, event_at) SELECT value_2, value_4, value_1, value_3, tenant_id, (random() OPERATOR(pg_catalog.*) (100)::double precision) AS value_6, (now())::date AS event_at FROM (SELECT raw_events_1.value_2, raw_events_1.value_4, raw_events_1.tenant_id, raw_events_1.value_1, raw_events_1.value_3 FROM public.raw_events_1) foo + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- use a column multiple times +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1(tenant_id, value_7, value_4) +SELECT + tenant_id, value_7, value_7 +FROM + raw_events_1 +ORDER BY + value_2, value_1; +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, value_7, event_at) SELECT tenant_id, value_7, 10 AS value_6, value_7, (now())::date AS event_at FROM public.raw_events_1 ORDER BY value_2, value_1 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + +-- test dropped table as well +ALTER TABLE raw_events_1 DROP COLUMN value_5; +SELECT deparse_shard_query_test(' +INSERT INTO raw_events_1(tenant_id, value_7, value_4) +SELECT + tenant_id, value_7, value_4 +FROM + raw_events_1; +'); +INFO: query: INSERT INTO public.raw_events_1 (tenant_id, value_4, value_6, value_7, event_at) SELECT tenant_id, value_4, 10 AS value_6, value_7, (now())::date AS event_at FROM public.raw_events_1 + deparse_shard_query_test +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/multi_explain.out b/src/test/regress/expected/multi_explain.out index 8d4d00d36..1c985ecce 100644 --- a/src/test/regress/expected/multi_explain.out +++ b/src/test/regress/expected/multi_explain.out @@ -301,12 +301,14 @@ Sort Group Key: l_quantity -> Seq Scan on lineitem_360000 lineitem -- Test analyze (with TIMING FALSE and SUMMARY FALSE for consistent output) +SELECT public.plan_normalize_memory($Q$ EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE) SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity; +$Q$); Sort (actual rows=50 loops=1) Sort Key: (COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint)), remote_scan.l_quantity - Sort Method: quicksort Memory: 27kB + Sort Method: quicksort Memory: xxx -> HashAggregate (actual rows=50 loops=1) Group Key: remote_scan.l_quantity -> Custom Scan (Citus Adaptive) (actual rows=100 loops=1) @@ -369,13 +371,15 @@ Custom Scan (Citus Adaptive) (actual rows=1 loops=1) END; DROP TABLE t1, t2; -- Test query text output, with ANALYZE ON +SELECT public.plan_normalize_memory($Q$ EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE, VERBOSE TRUE) SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity; +$Q$); Sort (actual rows=50 loops=1) Output: remote_scan.l_quantity, (COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint)) Sort Key: (COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint)), remote_scan.l_quantity - Sort Method: quicksort Memory: 27kB + Sort Method: quicksort Memory: xxx -> HashAggregate (actual rows=50 loops=1) Output: remote_scan.l_quantity, COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint) Group Key: remote_scan.l_quantity @@ -632,6 +636,21 @@ Aggregate -> Seq Scan on events_1400285 events Filter: ((event_type)::text = ANY ('{click,submit,pay}'::text[])) -- Union and left join subquery pushdown +-- enable_group_by_reordering is a new GUC introduced in PG15 +-- it does some optimization of the order of group by keys which results +-- in a different explain output plan between PG13/14 and PG15 +-- Hence we set that GUC to off. +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +SET enable_group_by_reordering TO off; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM SET enable_group_by_reordering TO off;$$); +1 +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); +(localhost,57637,t,t) +(localhost,57638,t,t) EXPLAIN (COSTS OFF) SELECT avg(array_length(events, 1)) AS event_average, @@ -854,6 +873,14 @@ Sort Sort Key: events_2.composite_id -> Seq Scan on events_1400285 events_2 Filter: ((composite_id >= '(1,-9223372036854775808)'::user_composite_type) AND (composite_id <= '(1,9223372036854775807)'::user_composite_type) AND ((event_type)::text = 'pay'::text)) +\if :server_version_ge_15 +RESET enable_group_by_reordering; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM RESET enable_group_by_reordering;$$); +1 +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); +(localhost,57637,t,t) +(localhost,57638,t,t) -- Lateral join subquery pushdown -- set subquery_pushdown due to limit in the query SET citus.subquery_pushdown to ON; @@ -1023,12 +1050,14 @@ Custom Scan (Citus Adaptive) -> Delete on lineitem_hash_part_360044 lineitem_hash_part -> Seq Scan on lineitem_hash_part_360044 lineitem_hash_part -- Test analyze (with TIMING FALSE and SUMMARY FALSE for consistent output) +SELECT public.plan_normalize_memory($Q$ EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE) SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity; +$Q$); Sort (actual rows=50 loops=1) Sort Key: (COALESCE((pg_catalog.sum(remote_scan.count_quantity))::bigint, '0'::bigint)), remote_scan.l_quantity - Sort Method: quicksort Memory: 27kB + Sort Method: quicksort Memory: xxx -> HashAggregate (actual rows=50 loops=1) Group Key: remote_scan.l_quantity -> Custom Scan (Citus Adaptive) (actual rows=100 loops=1) diff --git a/src/test/regress/expected/multi_extension.out b/src/test/regress/expected/multi_extension.out index f0e061d1e..1d2466a2e 100644 --- a/src/test/regress/expected/multi_extension.out +++ b/src/test/regress/expected/multi_extension.out @@ -724,6 +724,14 @@ SELECT * FROM multi_extension.print_extension_changes(); -- recreate public schema, and recreate citus_tables in the public schema by default CREATE SCHEMA public; +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +ALTER SCHEMA public OWNER TO pg_database_owner; +\endif GRANT ALL ON SCHEMA public TO public; ALTER EXTENSION citus UPDATE TO '9.5-1'; ALTER EXTENSION citus UPDATE TO '10.0-4'; @@ -1103,7 +1111,7 @@ ERROR: extension "citus" already exists -- Snapshot of state at 11.1-1 ALTER EXTENSION citus UPDATE TO '11.1-1'; SELECT * FROM multi_extension.print_extension_changes(); - previous_object | current_object + previous_object | current_object --------------------------------------------------------------------- access method columnar | function alter_columnar_table_reset(regclass,boolean,boolean,boolean,boolean) void | @@ -1128,12 +1136,15 @@ SELECT * FROM multi_extension.print_extension_changes(); table columnar.chunk_group | table columnar.options | table columnar.stripe | + | function citus_internal_delete_partition_metadata(regclass) void | function citus_locks() SETOF record | function citus_split_shard_by_split_points(bigint,text[],integer[],citus.shard_transfer_mode) void + | function create_distributed_table_concurrently(regclass,text,citus.distribution_type,text,integer) void | function isolate_tenant_to_new_shard(regclass,"any",text,citus.shard_transfer_mode) bigint | function replicate_reference_tables(citus.shard_transfer_mode) void | function worker_copy_table_to_node(regclass,integer) void - | function worker_split_copy(bigint,split_copy_info[]) void + | function worker_split_copy(bigint,text,split_copy_info[]) void + | function worker_split_shard_release_dsm() void | function worker_split_shard_replication_setup(split_shard_info[]) SETOF replication_slot_info | sequence pg_dist_cleanup_recordid_seq | sequence pg_dist_operationid_seq 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 fa1b2c1ff..99f603541 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 @@ -8,6 +8,7 @@ SET citus.next_shard_id TO 910000; SET citus.shard_replication_factor TO 1; CREATE SCHEMA fix_idx_names; SET search_path TO fix_idx_names, public; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 1370000; -- stop metadata sync for one of the worker nodes so we test both cases SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); NOTICE: dropping metadata on the node (localhost,57637) @@ -520,9 +521,9 @@ SELECT tablename, indexname FROM pg_indexes WHERE schemaname = 'fix_idx_names' O tablename | indexname --------------------------------------------------------------------- date_partitioned_citus_local_table | date_partitioned_citus_local_table_measureid_idx - date_partitioned_citus_local_table_361377 | date_partitioned_citus_local_table_measureid_idx_361377 + date_partitioned_citus_local_table_361369 | date_partitioned_citus_local_table_measureid_idx_361369 partition_local_table | partition_local_table_measureid_idx - partition_local_table_361378 | partition_local_table_measureid_idx_361378 + partition_local_table_361370 | partition_local_table_measureid_idx_361370 (4 rows) -- creating a single object should only need to trigger fixing the single object @@ -698,9 +699,9 @@ NOTICE: issuing SET citus.enable_ddl_propagation TO 'off' 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 SELECT citus_internal_add_partition_metadata ('fix_idx_names.p2'::regclass, 'h', 'dist_col', 1370000, 's') +NOTICE: issuing SELECT citus_internal_add_partition_metadata ('fix_idx_names.p2'::regclass, 'h', 'dist_col', 1370001, 's') DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SELECT citus_internal_add_partition_metadata ('fix_idx_names.p2'::regclass, 'h', 'dist_col', 1370000, 's') +NOTICE: issuing SELECT citus_internal_add_partition_metadata ('fix_idx_names.p2'::regclass, 'h', 'dist_col', 1370001, 's') DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing WITH shard_data(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) AS (VALUES ('fix_idx_names.p2'::regclass, 915002, 't'::"char", '-2147483648', '2147483647')) SELECT citus_internal_add_shard_metadata(relationname, shardid, storagetype, shardminvalue, shardmaxvalue) FROM shard_data; DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx @@ -752,7 +753,7 @@ DETAIL: drop cascades to table not_partitioned drop cascades to table not_distributed drop cascades to table fk_table drop cascades to table p -drop cascades to table date_partitioned_citus_local_table_361377 +drop cascades to table date_partitioned_citus_local_table_361369 drop cascades to table date_partitioned_citus_local_table drop cascades to table parent_table SELECT citus_remove_node('localhost', :master_port); diff --git a/src/test/regress/expected/multi_function_in_join_0.out b/src/test/regress/expected/multi_function_in_join_0.out deleted file mode 100644 index 5f2bd70c7..000000000 --- a/src/test/regress/expected/multi_function_in_join_0.out +++ /dev/null @@ -1,265 +0,0 @@ --- --- multi function in join queries aims to test the function calls that are --- used in joins. --- --- These functions are supposed to be executed on the worker and to ensure --- that we wrap those functions inside (SELECT * FROM fnc()) sub queries. --- --- We do not yet support those functions that: --- - have lateral joins --- - have WITH ORDINALITY clause --- - are user-defined and immutable -CREATE SCHEMA functions_in_joins; -SET search_path TO 'functions_in_joins'; -SET citus.next_shard_id TO 2500000; -SET citus.shard_replication_factor to 1; -CREATE TABLE table1 (id int, data int); -SELECT create_distributed_table('table1','id'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -INSERT INTO table1 -SELECT x, x*x -from generate_series(1, 100) as f (x); --- Verbose messages for observing the subqueries that wrapped function calls -SET client_min_messages TO DEBUG1; --- Check joins on a sequence -CREATE SEQUENCE numbers; -SELECT * FROM table1 JOIN nextval('numbers') n ON (id = n) ORDER BY id ASC; -DEBUG: generating subplan XXX_1 for subquery SELECT n FROM nextval('functions_in_joins.numbers'::regclass) n(n) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, n.n FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.n FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(n bigint)) n ON ((table1.id OPERATOR(pg_catalog.=) n.n))) ORDER BY table1.id - id | data | n ---------------------------------------------------------------------- - 1 | 1 | 1 -(1 row) - --- Check joins of a function that returns a single integer -CREATE FUNCTION add(integer, integer) RETURNS integer -AS 'SELECT $1 + $2;' -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 add(3,5) sum ON (id = sum) ORDER BY id ASC; -DEBUG: generating subplan XXX_1 for subquery SELECT sum FROM functions_in_joins.add(3, 5) sum(sum) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, sum.sum FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.sum FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(sum integer)) sum ON ((table1.id OPERATOR(pg_catalog.=) sum.sum))) ORDER BY table1.id - id | data | sum ---------------------------------------------------------------------- - 8 | 64 | 8 -(1 row) - --- Check join of plpgsql functions --- a function returning a single integer -CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$ -BEGIN - RETURN i + 1; -END; -$$ 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 ---------------------------------------------------------------------- - 3 | 9 | 3 -(1 row) - --- a function that returns a set of integers --- Block distributing function as we have tests below to test it locally -SET citus.enable_metadata_sync TO OFF; -CREATE OR REPLACE FUNCTION next_k_integers(IN first_value INTEGER, - IN k INTEGER DEFAULT 3, - OUT result INTEGER) - RETURNS SETOF INTEGER AS $$ -BEGIN - RETURN QUERY SELECT x FROM generate_series(first_value, first_value+k-1) f(x); -END; -$$ LANGUAGE plpgsql; -RESET citus.enable_metadata_sync; -SELECT * -FROM table1 JOIN next_k_integers(3,2) next_integers ON (id = next_integers.result) -ORDER BY id ASC; -DEBUG: generating subplan XXX_1 for subquery SELECT result FROM functions_in_joins.next_k_integers(3, 2) next_integers(result) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, next_integers.result FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.result FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(result integer)) next_integers ON ((table1.id OPERATOR(pg_catalog.=) next_integers.result))) ORDER BY table1.id - id | data | result ---------------------------------------------------------------------- - 3 | 9 | 3 - 4 | 16 | 4 -(2 rows) - --- a function returning set of records -CREATE FUNCTION get_set_of_records() RETURNS SETOF RECORD AS $cmd$ -SELECT x, x+1 FROM generate_series(0,4) f(x) -$cmd$ -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 ---------------------------------------------------------------------- - 1 | 1 | 1 | 2 - 2 | 4 | 2 | 3 - 3 | 9 | 3 | 4 - 4 | 16 | 4 | 5 -(4 rows) - --- a function returning table -CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text) -AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$ -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 ---------------------------------------------------------------------- - 32 | 32 is text -(1 row) - --- a stable function -CREATE OR REPLACE FUNCTION the_minimum_id() - RETURNS INTEGER STABLE AS 'SELECT min(id) FROM table1' 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 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 ---------------------------------------------------------------------- - 1 | 1 | 1 -(1 row) - --- a built-in immutable function -SELECT * FROM table1 JOIN abs(100) as hundred ON (id = hundred) ORDER BY id ASC; - id | data | hundred ---------------------------------------------------------------------- - 100 | 10000 | 100 -(1 row) - --- function joins inside a CTE -WITH next_row_to_process AS ( - SELECT * FROM table1 JOIN nextval('numbers') n ON (id = n) - ) -SELECT * -FROM table1, next_row_to_process -WHERE table1.data <= next_row_to_process.data -ORDER BY 1,2 ASC; -DEBUG: generating subplan XXX_1 for CTE next_row_to_process: SELECT table1.id, table1.data, n.n FROM (functions_in_joins.table1 JOIN nextval('functions_in_joins.numbers'::regclass) n(n) ON ((table1.id OPERATOR(pg_catalog.=) n.n))) -DEBUG: generating subplan XXX_1 for subquery SELECT n FROM nextval('functions_in_joins.numbers'::regclass) n(n) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, n.n FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.n FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(n bigint)) n ON ((table1.id OPERATOR(pg_catalog.=) n.n))) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT table1.id, table1.data, next_row_to_process.id, next_row_to_process.data, next_row_to_process.n FROM functions_in_joins.table1, (SELECT intermediate_result.id, intermediate_result.data, intermediate_result.n FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, data integer, n bigint)) next_row_to_process WHERE (table1.data OPERATOR(pg_catalog.<=) next_row_to_process.data) ORDER BY table1.id, table1.data - id | data | id | data | n ---------------------------------------------------------------------- - 1 | 1 | 2 | 4 | 2 - 2 | 4 | 2 | 4 | 2 -(2 rows) - --- Multiple functions in an RTE -SELECT * FROM ROWS FROM (next_k_integers(5), next_k_integers(10)) AS f(a, b), - table1 WHERE id = a ORDER BY id ASC; -DEBUG: generating subplan XXX_1 for subquery SELECT a, b FROM ROWS FROM(functions_in_joins.next_k_integers(5), functions_in_joins.next_k_integers(10)) f(a, b) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT f.a, f.b, table1.id, table1.data 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)) f(a, b), functions_in_joins.table1 WHERE (table1.id OPERATOR(pg_catalog.=) f.a) ORDER BY table1.id - a | b | id | data ---------------------------------------------------------------------- - 5 | 10 | 5 | 25 - 6 | 11 | 6 | 36 - 7 | 12 | 7 | 49 -(3 rows) - --- Custom Type returning function used in a join -RESET client_min_messages; -CREATE TYPE min_and_max AS ( - minimum INT, - maximum INT -); -SET client_min_messages TO DEBUG1; -CREATE OR REPLACE FUNCTION max_and_min () RETURNS - min_and_max AS $$ -DECLARE - result min_and_max%rowtype; -begin - select into result min(data) as minimum, max(data) as maximum from table1; - return result; -end; -$$ 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 ---------------------------------------------------------------------- - 1 | 1 | 1 | 10000 - 100 | 10000 | 1 | 10000 -(2 rows) - --- The following tests will fail as we do not support all joins on --- all kinds of functions --- In other words, we cannot recursively plan the functions and hence --- the query fails on the workers -SET client_min_messages TO ERROR; -\set VERBOSITY terse --- function joins in CTE results can create lateral joins that are not supported --- we execute the query within a function to consolidate the error messages --- between different executors -SET citus.enable_metadata_sync TO OFF; -CREATE FUNCTION raise_failed_execution_func_join(query text) RETURNS void AS $$ -BEGIN - EXECUTE query; - EXCEPTION WHEN OTHERS THEN - IF SQLERRM LIKE 'failed to execute task%' THEN - RAISE 'Task failed to execute'; - ELSIF SQLERRM LIKE '%does not exist%' THEN - RAISE 'Task failed to execute'; - END IF; -END; -$$LANGUAGE plpgsql; -RESET citus.enable_metadata_sync; -SELECT raise_failed_execution_func_join($$ - WITH one_row AS ( - SELECT * FROM table1 WHERE id=52 - ) - SELECT table1.id, table1.data - FROM one_row, table1, next_k_integers(one_row.id, 5) next_five_ids - WHERE table1.id = next_five_ids; -$$); -ERROR: Task failed to execute --- a user-defined immutable function -SET citus.enable_metadata_sync TO OFF; -CREATE OR REPLACE FUNCTION the_answer_to_life() - RETURNS INTEGER IMMUTABLE AS 'SELECT 42' LANGUAGE SQL; -RESET citus.enable_metadata_sync; -SELECT raise_failed_execution_func_join($$ - SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer); -$$); -ERROR: Task failed to execute -SELECT raise_failed_execution_func_join($$ - SELECT * - FROM table1 - JOIN next_k_integers(10,5) WITH ORDINALITY next_integers - ON (id = next_integers.result); -$$); -ERROR: Task failed to execute --- WITH ORDINALITY clause -SELECT raise_failed_execution_func_join($$ - SELECT * - FROM table1 - JOIN next_k_integers(10,5) WITH ORDINALITY next_integers - ON (id = next_integers.result) - ORDER BY id ASC; -$$); -ERROR: Task failed to execute -RESET client_min_messages; -DROP SCHEMA functions_in_joins CASCADE; -NOTICE: drop cascades to 12 other objects -SET search_path TO DEFAULT; diff --git a/src/test/regress/expected/multi_insert_select.out b/src/test/regress/expected/multi_insert_select.out index f02c58fb1..b1bf7a5e6 100644 --- a/src/test/regress/expected/multi_insert_select.out +++ b/src/test/regress/expected/multi_insert_select.out @@ -1,6 +1,17 @@ -- -- MULTI_INSERT_SELECT -- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + SET citus.next_shard_id TO 13300000; SET citus.next_placement_id TO 13300000; -- create co-located tables @@ -63,10 +74,10 @@ INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) SET client_min_messages TO DEBUG2; -- raw table to raw table INSERT INTO raw_events_second SELECT * FROM raw_events_first; -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_first.user_id, raw_events_first."time", raw_events_first.value_1, raw_events_first.value_2, raw_events_first.value_3, raw_events_first.value_4 FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_first.user_id, raw_events_first."time", raw_events_first.value_1, raw_events_first.value_2, raw_events_first.value_3, raw_events_first.value_4 FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_first.user_id, raw_events_first."time", raw_events_first.value_1, raw_events_first.value_2, raw_events_first.value_3, raw_events_first.value_4 FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_first.user_id, raw_events_first."time", raw_events_first.value_1, raw_events_first.value_2, raw_events_first.value_3, raw_events_first.value_4 FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) -- see that our first multi shard INSERT...SELECT works expected SET client_min_messages TO INFO; SELECT @@ -152,7 +163,7 @@ INSERT INTO raw_events_first (user_id, time) VALUES SET client_min_messages TO DEBUG2; INSERT INTO raw_events_second (user_id, time) SELECT user_id, time FROM raw_events_first WHERE user_id = 7; DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, "time") SELECT user_id, "time" FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) 7) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, "time") SELECT raw_events_first.user_id, raw_events_first."time" FROM public.raw_events_first_13300001 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) 7) AND (raw_events_first.user_id IS NOT NULL)) DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away SET client_min_messages TO INFO; @@ -168,7 +179,7 @@ FROM raw_events_first WHERE user_id = 8; -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) 8) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_first.user_id, raw_events_first."time", raw_events_first.value_1, raw_events_first.value_2, raw_events_first.value_3, raw_events_first.value_4 FROM public.raw_events_first_13300000 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) 8) AND (raw_events_first.user_id IS NOT NULL)) DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away @@ -210,10 +221,10 @@ FROM WHERE value_3 = 9000 RETURNING *; -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300000 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300001 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300002 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300003 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, value_1, value_3) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first.value_3 FROM public.raw_events_first_13300000 raw_events_first WHERE ((raw_events_first.value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (raw_events_first.user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, value_1, value_3) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first.value_3 FROM public.raw_events_first_13300001 raw_events_first WHERE ((raw_events_first.value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (raw_events_first.user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id, value_1, value_3) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first.value_3 FROM public.raw_events_first_13300002 raw_events_first WHERE ((raw_events_first.value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (raw_events_first.user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, value_1, value_3) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first.value_3 FROM public.raw_events_first_13300003 raw_events_first WHERE ((raw_events_first.value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (raw_events_first.user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 user_id | time | value_1 | value_2 | value_3 | value_4 --------------------------------------------------------------------- 9 | | 90 | | 9000 | @@ -230,9 +241,9 @@ WHERE user_id = 9 OR user_id = 16 RETURNING *; DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300001 raw_events_first WHERE (((user_id OPERATOR(pg_catalog.=) 9) OR (user_id OPERATOR(pg_catalog.=) 16)) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, value_1, value_3) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first.value_3 FROM public.raw_events_first_13300001 raw_events_first WHERE (((raw_events_first.user_id OPERATOR(pg_catalog.=) 9) OR (raw_events_first.user_id OPERATOR(pg_catalog.=) 16)) AND (raw_events_first.user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300003 raw_events_first WHERE (((user_id OPERATOR(pg_catalog.=) 9) OR (user_id OPERATOR(pg_catalog.=) 16)) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, value_1, value_3) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first.value_3 FROM public.raw_events_first_13300003 raw_events_first WHERE (((raw_events_first.user_id OPERATOR(pg_catalog.=) 9) OR (raw_events_first.user_id OPERATOR(pg_catalog.=) 16)) AND (raw_events_first.user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 ERROR: duplicate key value violates unique constraint "raw_events_second_user_id_value_1_key_xxxxxxx" -- now do some aggregations INSERT INTO agg_events @@ -242,10 +253,10 @@ FROM raw_events_first GROUP BY user_id; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, avg(raw_events_first.value_2) AS avg, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, avg(raw_events_first.value_2) AS avg, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, avg(raw_events_first.value_2) AS avg, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, avg(raw_events_first.value_2) AS avg, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id -- group by column not exists on the SELECT target list INSERT INTO agg_events (value_3_agg, value_4_agg, value_1_agg, user_id) SELECT @@ -255,10 +266,10 @@ FROM GROUP BY value_2, user_id RETURNING *; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.value_2, raw_events_first.user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.value_2, raw_events_first.user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.value_2, raw_events_first.user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum, sum(raw_events_first.value_3) AS sum, count(raw_events_first.value_4) AS count FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.value_2, raw_events_first.user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time ERROR: duplicate key value violates unique constraint "agg_events_user_id_value_1_agg_key_xxxxxxx" -- some subquery tests INSERT INTO agg_events @@ -273,10 +284,10 @@ FROM (SELECT raw_events_second.user_id AS id, WHERE raw_events_first.user_id = raw_events_second.user_id) AS foo GROUP BY id ORDER BY id; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT foo.id, sum(foo.value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (foo.id IS NOT NULL) GROUP BY foo.id ORDER BY foo.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT foo.id, sum(foo.value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (foo.id IS NOT NULL) GROUP BY foo.id ORDER BY foo.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT foo.id, sum(foo.value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (foo.id IS NOT NULL) GROUP BY foo.id ORDER BY foo.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT foo.id, sum(foo.value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (foo.id IS NOT NULL) GROUP BY foo.id ORDER BY foo.id ERROR: duplicate key value violates unique constraint "agg_events_user_id_value_1_agg_key_xxxxxxx" -- subquery one more level depth INSERT INTO agg_events @@ -294,10 +305,10 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, WHERE raw_events_first.user_id = raw_events_second.user_id GROUP BY raw_events_second.user_id) AS foo ORDER BY id; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT foo.id, foo.v1, foo.v4 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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (foo.id IS NOT NULL) ORDER BY foo.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT foo.id, foo.v1, foo.v4 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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (foo.id IS NOT NULL) ORDER BY foo.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT foo.id, foo.v1, foo.v4 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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (foo.id IS NOT NULL) ORDER BY foo.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT foo.id, foo.v1, foo.v4 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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (foo.id IS NOT NULL) ORDER BY foo.id ERROR: duplicate key value violates unique constraint "agg_events_user_id_value_1_agg_key_xxxxxxx" \set VERBOSITY DEFAULT -- join between subqueries @@ -356,10 +367,10 @@ FROM ON (f.id = f2.id)) as outer_most GROUP BY outer_most.id; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id -- subqueries in WHERE clause INSERT INTO raw_events_second (user_id) @@ -371,7 +382,7 @@ WHERE user_id IN (SELECT user_id DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) 2))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) 2))) AND (raw_events_first.user_id IS NOT NULL)) INSERT INTO raw_events_second (user_id) SELECT user_id @@ -380,10 +391,10 @@ WHERE user_id IN (SELECT user_id FROM raw_events_second WHERE user_id != 2 AND value_1 = 2000) ON conflict (user_id, value_1) DO NOTHING; -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (raw_events_first.user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (raw_events_first.user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (raw_events_first.user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (raw_events_first.user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING INSERT INTO raw_events_second (user_id) SELECT user_id @@ -401,10 +412,10 @@ FROM raw_events_first WHERE user_id IN (SELECT user_id FROM raw_events_second WHERE value_1 = 1000 OR value_1 = 2000 OR value_1 = 3000); -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (raw_events_first.user_id IS NOT NULL)) -- lets mix subqueries in FROM clause and subqueries in WHERE INSERT INTO agg_events (user_id) @@ -449,10 +460,10 @@ ON conflict (user_id, value_1_agg) DO UPDATE SET agg_time = EXCLUDED.agg_time WHERE ae.agg_time < EXCLUDED.agg_time; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) -- upserts with returning INSERT INTO agg_events AS ae ( @@ -469,10 +480,10 @@ DO UPDATE SET agg_time = EXCLUDED.agg_time WHERE ae.agg_time < EXCLUDED.agg_time RETURNING user_id, value_1_agg; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS ae (user_id, value_1_agg, agg_time) SELECT raw_events_first.user_id, raw_events_first.value_1, raw_events_first."time" FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg user_id | value_1_agg --------------------------------------------------------------------- 7 | @@ -483,20 +494,20 @@ SELECT user_id, sum(value_1 + value_2) FROM raw_events_first GROUP BY user_id; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) AS sum FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) AS sum FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) AS sum FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) AS sum FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id -- FILTER CLAUSE INSERT INTO agg_events (user_id, value_1_agg) SELECT user_id, sum(value_1 + value_2) FILTER (where value_3 = 15) FROM raw_events_first GROUP BY user_id; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) FILTER (WHERE (raw_events_first.value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) FILTER (WHERE (raw_events_first.value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) FILTER (WHERE (raw_events_first.value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum((raw_events_first.value_1 OPERATOR(pg_catalog.+) raw_events_first.value_2)) FILTER (WHERE (raw_events_first.value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) GROUP BY raw_events_first.user_id -- a test with reference table JOINs INSERT INTO agg_events (user_id, value_1_agg) @@ -591,10 +602,10 @@ INSERT INTO agg_events (value_1_agg, user_id) DISTINCT value_1, user_id FROM raw_events_first; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) -- we don't want to see constraint violations, so truncate first SET client_min_messages TO INFO; truncate agg_events; @@ -636,10 +647,10 @@ INSERT INTO agg_events (value_1_agg, user_id) DISTINCT ON (user_id) value_1, user_id FROM raw_events_first; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (raw_events_first.user_id) raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300000 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (raw_events_first.user_id) raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300001 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (raw_events_first.user_id) raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300002 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (raw_events_first.user_id) raw_events_first.user_id, raw_events_first.value_1 FROM public.raw_events_first_13300003 raw_events_first WHERE (raw_events_first.user_id IS NOT NULL) SELECT user_id, value_1_agg FROM agg_events ORDER BY 1,2; DEBUG: Router planner cannot handle multi-shard select queries user_id | value_1_agg @@ -685,10 +696,10 @@ DEBUG: Subqueries without relations are not allowed in distributed INSERT ... S 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 public.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 public.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 public.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 public.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) +DEBUG: distributed statement: INSERT INTO public.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 public.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 public.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 public.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 BEGIN; INSERT INTO @@ -702,10 +713,10 @@ 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 public.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 public.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 public.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 public.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 public.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 public.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 public.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 public.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) ROLLBACK; -- We do support set operations through recursive planning BEGIN; @@ -1174,10 +1185,10 @@ HINT: Ensure the target table's partition column has a corresponding simple col 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 public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0,repartitioned_results_xxxxx_from_13300001_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_1,repartitioned_results_xxxxx_from_13300001_to_1,repartitioned_results_xxxxx_from_13300003_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_3,repartitioned_results_xxxxx_from_13300002_to_3,repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0,repartitioned_results_xxxxx_from_13300001_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_1,repartitioned_results_xxxxx_from_13300001_to_1,repartitioned_results_xxxxx_from_13300003_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_3,repartitioned_results_xxxxx_from_13300002_to_3,repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) INSERT INTO raw_events_second (user_id) SELECT user_id :: bigint @@ -1188,10 +1199,10 @@ HINT: Ensure the target table's partition column has a corresponding simple col 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 public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT intermediate_result.user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) INSERT INTO agg_events (value_3_agg, value_4_agg, @@ -1637,7 +1648,7 @@ 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); -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second, public.raw_events_first_13300000 raw_events_first_1 WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first_1.user_id) AND (raw_events_first_1.user_id OPERATOR(pg_catalog.=) 200)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second, public.raw_events_first_13300000 raw_events_first_1 WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first_1.user_id) AND (raw_events_first_1.user_id OPERATOR(pg_catalog.=) 200)))) AND (raw_events_first.user_id IS NOT NULL)) DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away @@ -1673,10 +1684,10 @@ FROM raw_events_first WHERE EXISTS (SELECT 1 FROM raw_events_second WHERE raw_events_second.user_id =raw_events_first.user_id); -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (raw_events_first.user_id IS NOT NULL)) -- we cannot push down INSERT INTO raw_events_second (user_id) @@ -1685,10 +1696,10 @@ FROM raw_events_first WHERE NOT EXISTS (SELECT 1 FROM raw_events_second WHERE raw_events_second.user_id =raw_events_first.user_id); -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (raw_events_first.user_id IS NOT NULL)) -- more complex LEFT JOINs INSERT INTO agg_events (user_id, value_4_agg) @@ -1718,10 +1729,10 @@ DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS ON (f.id = f2.id)) as outer_most GROUP BY outer_most.id; -DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300000 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300001 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300002 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id -DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300003 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300000 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300001 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300002 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_4_agg) SELECT outer_most.id, max(outer_most.value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300003 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (outer_most.id IS NOT NULL) GROUP BY outer_most.id RESET client_min_messages; -- cannot push down since the f.id IN is matched with value_1 -- we use repartition insert/select instead @@ -1795,10 +1806,10 @@ FROM (SELECT SUM(raw_events_second.value_4) AS v4, ON (f.id = f2.id) WHERE f.id IN (SELECT user_id FROM raw_events_second)); -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300000 raw_events_first_1, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300001 raw_events_first_1, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300002 raw_events_first_1, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second)))) AND (user_id IS NOT NULL)) -DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300003 raw_events_first_1, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300000 raw_events_first_1, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300001 raw_events_first_1, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300002 raw_events_first_1, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second)))) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300003 raw_events_first_1, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second)))) AND (raw_events_first.user_id IS NOT NULL)) RESET client_min_messages; -- cannot push down since top level user_id is matched with NOT IN INSERT INTO raw_events_second @@ -2004,16 +2015,16 @@ truncate raw_events_first; SET client_min_messages TO DEBUG2; -- first show that the query works now INSERT INTO raw_events_first SELECT * FROM raw_events_second; -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300005 raw_events_second WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300007 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) SET client_min_messages TO INFO; truncate raw_events_first; SET client_min_messages TO DEBUG2; -- now show that it works for a single shard query as well INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 5; -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE ((user_id OPERATOR(pg_catalog.=) 5) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) 5) AND (raw_events_second.user_id IS NOT NULL)) DEBUG: Skipping target shard interval 13300001 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300002 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300003 since SELECT query for it pruned away @@ -2034,7 +2045,7 @@ DETAIL: Insert query cannot be executed on all placements for shard xxxxx INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 6; DEBUG: Skipping target shard interval 13300000 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300001 since SELECT query for it pruned away -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE ((user_id OPERATOR(pg_catalog.=) 6) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) 6) AND (raw_events_second.user_id IS NOT NULL)) DEBUG: Skipping target shard interval 13300003 since SELECT query for it pruned away SET client_min_messages TO INFO; -- mark the unhealthy placement as healthy again for the next tests @@ -2045,16 +2056,16 @@ truncate raw_events_first; SET client_min_messages TO DEBUG2; -- this should work INSERT INTO raw_events_first SELECT * FROM raw_events_second; -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300005 raw_events_second WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE (user_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300007 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id IS NOT NULL) SET client_min_messages TO INFO; truncate raw_events_first; SET client_min_messages TO DEBUG2; -- this should also work INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 5; -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE ((user_id OPERATOR(pg_catalog.=) 5) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT raw_events_second.user_id, raw_events_second."time", raw_events_second.value_1, raw_events_second.value_2, raw_events_second.value_3, raw_events_second.value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) 5) AND (raw_events_second.user_id IS NOT NULL)) DEBUG: Skipping target shard interval 13300001 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300002 since SELECT query for it pruned away DEBUG: Skipping target shard interval 13300003 since SELECT query for it pruned away @@ -2108,64 +2119,64 @@ SELECT create_distributed_table('table_with_defaults', 'store_id'); SET client_min_messages TO DEBUG2; -- a very simple query INSERT INTO table_with_defaults SELECT * FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, default_1, last_name, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, default_1, last_name, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_1, table_with_defaults.last_name, table_with_defaults.default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_1, table_with_defaults.last_name, table_with_defaults.default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- see that defaults are filled INSERT INTO table_with_defaults (store_id, first_name) SELECT store_id, first_name FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, 1 AS default_1, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, 1 AS default_1, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- shuffle one of the defaults and skip the other INSERT INTO table_with_defaults (default_2, store_id, first_name) SELECT default_2, store_id, first_name FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, 1 AS default_1, table_with_defaults.default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, 1 AS default_1, table_with_defaults.default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- shuffle both defaults INSERT INTO table_with_defaults (default_2, store_id, default_1, first_name) SELECT default_2, store_id, default_1, first_name FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, default_1, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, default_1, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_1, table_with_defaults.default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_1, table_with_defaults.default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- use constants instead of non-default column INSERT INTO table_with_defaults (default_2, last_name, store_id, first_name) SELECT default_2, 'Freund', store_id, 'Andres' FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, table_with_defaults.default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, table_with_defaults.default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- use constants instead of non-default column and skip both defauls INSERT INTO table_with_defaults (last_name, store_id, first_name) SELECT 'Freund', store_id, 'Andres' FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- use constants instead of default columns INSERT INTO table_with_defaults (default_2, last_name, store_id, first_name, default_1) SELECT 20, last_name, store_id, first_name, 10 FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, 10, last_name, 20 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, 10, last_name, 20 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, 10, table_with_defaults.last_name, 20 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, table_with_defaults.first_name, 10, table_with_defaults.last_name, 20 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- use constants instead of both default columns and non-default columns INSERT INTO table_with_defaults (default_2, last_name, store_id, first_name, default_1) SELECT 20, 'Freund', store_id, 'Andres', 10 FROM table_with_defaults; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 10, 'Freund'::text AS last_name, 20 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 10, 'Freund'::text AS last_name, 20 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 10, 'Freund'::text AS last_name, 20 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 10, 'Freund'::text AS last_name, 20 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) -- some of the ultimate queries where we have constants, -- defaults and group by entry is not on the target entry INSERT INTO table_with_defaults (default_2, store_id, first_name) @@ -2175,8 +2186,8 @@ FROM table_with_defaults GROUP BY last_name, store_id; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1 AS default_1, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1 AS default_1, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id INSERT INTO table_with_defaults (default_1, store_id, first_name, default_2) SELECT 1000, store_id, 'Andres', '2000' @@ -2184,8 +2195,8 @@ FROM table_with_defaults GROUP BY last_name, store_id, first_name; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id, table_with_defaults.first_name +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id, table_with_defaults.first_name INSERT INTO table_with_defaults (default_1, store_id, first_name, default_2) SELECT 1000, store_id, 'Andres', '2000' @@ -2193,8 +2204,8 @@ FROM table_with_defaults GROUP BY last_name, store_id, first_name, default_2; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_2 +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_2 INSERT INTO table_with_defaults (default_1, store_id, first_name) SELECT 1000, store_id, 'Andres' @@ -2202,8 +2213,8 @@ FROM table_with_defaults GROUP BY last_name, store_id, first_name, default_2; -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 -DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1000, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_2 +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT table_with_defaults.store_id, 'Andres'::text AS first_name, 1000, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (table_with_defaults.store_id IS NOT NULL) GROUP BY table_with_defaults.last_name, table_with_defaults.store_id, table_with_defaults.first_name, table_with_defaults.default_2 RESET client_min_messages; -- Stable function in default should be allowed ALTER TABLE table_with_defaults ADD COLUMN t timestamptz DEFAULT now(); @@ -2423,20 +2434,20 @@ SELECT s, nextval('insert_select_test_seq') FROM generate_series(1, 5) s ON CONFLICT DO NOTHING; DEBUG: distributed INSERT ... SELECT can only select from distributed tables DEBUG: Collecting INSERT ... SELECT results on coordinator -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300000'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300001'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300002'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300003'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300000'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300001'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300002'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300003'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING -- RETURNING is supported INSERT INTO raw_events_first (user_id, value_1) SELECT s, nextval('insert_select_test_seq') FROM generate_series(1, 5) s RETURNING *; DEBUG: distributed INSERT ... SELECT can only select from distributed tables DEBUG: Collecting INSERT ... SELECT results on coordinator -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300000'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300001'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300002'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 -DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300003'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300000'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300001'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300002'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, value_1) SELECT intermediate_result.user_id, intermediate_result.value_1 FROM read_intermediate_result('insert_select_XXX_13300003'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 user_id | time | value_1 | value_2 | value_3 | value_4 --------------------------------------------------------------------- 1 | | 11 | | | diff --git a/src/test/regress/expected/multi_insert_select_0.out b/src/test/regress/expected/multi_insert_select_0.out new file mode 100644 index 000000000..2947f8be0 --- /dev/null +++ b/src/test/regress/expected/multi_insert_select_0.out @@ -0,0 +1,3307 @@ +-- +-- MULTI_INSERT_SELECT +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +SET citus.next_shard_id TO 13300000; +SET citus.next_placement_id TO 13300000; +-- create co-located tables +SET citus.shard_count = 4; +SET citus.shard_replication_factor = 2; +-- order of execution might change in parallel executions +-- and the error details might contain the worker node +-- so be less verbose with \set VERBOSITY TERSE when necessary +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', 'user_id'); + 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', 'user_id'); + 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', 'user_id');; + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- create the reference table as well +CREATE TABLE reference_table (user_id int); +SELECT create_reference_table('reference_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE insert_select_varchar_test (key varchar, value int); +SELECT create_distributed_table('insert_select_varchar_test', 'key', 'hash'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- set back to the defaults +SET citus.shard_count = DEFAULT; +SET citus.shard_replication_factor = DEFAULT; +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (1, now(), 10, 100, 1000.1, 10000); +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (2, now(), 20, 200, 2000.1, 20000); +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (3, now(), 30, 300, 3000.1, 30000); +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (4, now(), 40, 400, 4000.1, 40000); +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (5, now(), 50, 500, 5000.1, 50000); +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (6, now(), 60, 600, 6000.1, 60000); +SET client_min_messages TO DEBUG2; +-- raw table to raw table +INSERT INTO raw_events_second SELECT * FROM raw_events_first; +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) +-- see that our first multi shard INSERT...SELECT works expected +SET client_min_messages TO INFO; +SELECT + raw_events_first.user_id +FROM + raw_events_first, raw_events_second +WHERE + raw_events_first.user_id = raw_events_second.user_id +ORDER BY + user_id DESC; + user_id +--------------------------------------------------------------------- + 6 + 5 + 4 + 3 + 2 + 1 +(6 rows) + +-- see that we get unique vialitons +\set VERBOSITY TERSE +INSERT INTO raw_events_second SELECT * FROM raw_events_first; +ERROR: duplicate key value violates unique constraint "raw_events_second_user_id_value_1_key_xxxxxxx" +\set VERBOSITY DEFAULT +-- stable functions should be allowed +INSERT INTO raw_events_second (user_id, time) +SELECT + user_id, now() +FROM + raw_events_first +WHERE + user_id < 0; +INSERT INTO raw_events_second (user_id) +SELECT + user_id +FROM + raw_events_first +WHERE + time > now() + interval '1 day'; +-- hide version-dependent PL/pgSQL context messages +\set VERBOSITY terse +-- make sure we evaluate stable functions on the master, once +CREATE OR REPLACE FUNCTION evaluate_on_master() +RETURNS int LANGUAGE plpgsql STABLE +AS $function$ +BEGIN + RAISE NOTICE 'evaluating on master'; + RETURN 0; +END; +$function$; +INSERT INTO raw_events_second (user_id, value_1) +SELECT + user_id, evaluate_on_master() +FROM + raw_events_first +WHERE + user_id < 0; +NOTICE: evaluating on master +-- make sure we don't evaluate stable functions with column arguments +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE FUNCTION evaluate_on_master(x int) +RETURNS int LANGUAGE plpgsql STABLE +AS $function$ +BEGIN + RAISE NOTICE 'evaluating on master'; + RETURN x; +END; +$function$; +RESET citus.enable_metadata_sync; +INSERT INTO raw_events_second (user_id, value_1) +SELECT + user_id, evaluate_on_master(value_1) +FROM + raw_events_first +WHERE + user_id = 0; +ERROR: function public.evaluate_on_master(integer) does not exist +-- add one more row +INSERT INTO raw_events_first (user_id, time) VALUES + (7, now()); +-- try a single shard query +SET client_min_messages TO DEBUG2; +INSERT INTO raw_events_second (user_id, time) SELECT user_id, time FROM raw_events_first WHERE user_id = 7; +DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, "time") SELECT user_id, "time" FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) 7) AND (user_id IS NOT NULL)) +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away +SET client_min_messages TO INFO; +-- add one more row +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (8, now(), 80, 800, 8000, 80000); +-- reorder columns +SET client_min_messages TO DEBUG2; +INSERT INTO raw_events_second (value_2, value_1, value_3, value_4, user_id, time) +SELECT + value_2, value_1, value_3, value_4, user_id, time +FROM + raw_events_first +WHERE + user_id = 8; +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) 8) AND (user_id IS NOT NULL)) +DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away +-- a zero shard select +INSERT INTO raw_events_second (value_2, value_1, value_3, value_4, user_id, time) +SELECT + value_2, value_1, value_3, value_4, user_id, time +FROM + raw_events_first +WHERE + false; +DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away +-- another zero shard select +INSERT INTO raw_events_second (value_2, value_1, value_3, value_4, user_id, time) +SELECT + value_2, value_1, value_3, value_4, user_id, time +FROM + raw_events_first +WHERE + 0 != 0; +DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away +-- add one more row +SET client_min_messages TO INFO; +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (9, now(), 90, 900, 9000, 90000); +-- show that RETURNING also works +SET client_min_messages TO DEBUG2; +INSERT INTO raw_events_second (user_id, value_1, value_3) +SELECT + user_id, value_1, value_3 +FROM + raw_events_first +WHERE + value_3 = 9000 +RETURNING *; +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300000 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300001 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300002 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300003 raw_events_first WHERE ((value_3 OPERATOR(pg_catalog.=) (9000)::double precision) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 + user_id | time | value_1 | value_2 | value_3 | value_4 +--------------------------------------------------------------------- + 9 | | 90 | | 9000 | +(1 row) + +-- hits two shards +\set VERBOSITY TERSE +INSERT INTO raw_events_second (user_id, value_1, value_3) +SELECT + user_id, value_1, value_3 +FROM + raw_events_first +WHERE + user_id = 9 OR user_id = 16 +RETURNING *; +DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300001 raw_events_first WHERE (((user_id OPERATOR(pg_catalog.=) 9) OR (user_id OPERATOR(pg_catalog.=) 16)) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id, value_1, value_3) SELECT user_id, value_1, value_3 FROM public.raw_events_first_13300003 raw_events_first WHERE (((user_id OPERATOR(pg_catalog.=) 9) OR (user_id OPERATOR(pg_catalog.=) 16)) AND (user_id IS NOT NULL)) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +ERROR: duplicate key value violates unique constraint "raw_events_second_user_id_value_1_key_xxxxxxx" +-- now do some aggregations +INSERT INTO agg_events +SELECT + user_id, sum(value_1), avg(value_2), sum(value_3), count(value_4) +FROM + raw_events_first +GROUP BY + user_id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_2_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, avg(value_2) AS avg, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +-- group by column not exists on the SELECT target list +INSERT INTO agg_events (value_3_agg, value_4_agg, value_1_agg, user_id) +SELECT + sum(value_3), count(value_4), sum(value_1), user_id +FROM + raw_events_first +GROUP BY + value_2, user_id +RETURNING *; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_3_agg, value_4_agg) SELECT user_id, sum(value_1) AS sum, sum(value_3) AS sum, count(value_4) AS count FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY value_2, user_id RETURNING citus_table_alias.user_id, citus_table_alias.value_1_agg, citus_table_alias.value_2_agg, citus_table_alias.value_3_agg, citus_table_alias.value_4_agg, citus_table_alias.agg_time +ERROR: duplicate key value violates unique constraint "agg_events_user_id_value_1_agg_key_xxxxxxx" +-- some subquery tests +INSERT INTO agg_events + (value_1_agg, + user_id) +SELECT SUM(value_1), + id +FROM (SELECT raw_events_second.user_id AS id, + raw_events_second.value_1 + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_second.user_id) AS foo +GROUP BY id +ORDER BY id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT id, sum(value_1) AS sum FROM (SELECT raw_events_second.user_id AS id, raw_events_second.value_1 FROM public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id)) foo WHERE (id IS NOT NULL) GROUP BY id ORDER BY id +ERROR: duplicate key value violates unique constraint "agg_events_user_id_value_1_agg_key_xxxxxxx" +-- subquery one more level depth +INSERT INTO agg_events + (value_4_agg, + value_1_agg, + user_id) +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) AS foo +ORDER BY id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg, value_4_agg) SELECT id, v1, v4 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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id) foo WHERE (id IS NOT NULL) ORDER BY id +ERROR: duplicate key value violates unique constraint "agg_events_user_id_value_1_agg_key_xxxxxxx" +\set VERBOSITY DEFAULT +-- join between subqueries +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id = f2.id); +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f2.id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f2.id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f2.id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f2.id IS NOT NULL) +-- add one more level subqueris on top of subquery JOINs +INSERT INTO agg_events + (user_id, value_4_agg) +SELECT + outer_most.id, max(outer_most.value) +FROM +( + SELECT f2.id as id, f2.v4 as value FROM + (SELECT + id + FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id = f2.id)) as outer_most +GROUP BY + outer_most.id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +-- subqueries in WHERE clause +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN (SELECT user_id + FROM raw_events_second + WHERE user_id = 2); +DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) 2))) AND (user_id IS NOT NULL)) +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN (SELECT user_id + FROM raw_events_second + WHERE user_id != 2 AND value_1 = 2000) +ON conflict (user_id, value_1) DO NOTHING; +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.<>) 2) AND (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000)))) AND (user_id IS NOT NULL)) ON CONFLICT(user_id, value_1) DO NOTHING +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN (SELECT user_id + FROM raw_events_second WHERE false); +DEBUG: Skipping target shard interval 13300004 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN (SELECT user_id + FROM raw_events_second + WHERE value_1 = 1000 OR value_1 = 2000 OR value_1 = 3000); +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second WHERE ((raw_events_second.value_1 OPERATOR(pg_catalog.=) 1000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 2000) OR (raw_events_second.value_1 OPERATOR(pg_catalog.=) 3000)))) AND (user_id IS NOT NULL)) +-- lets mix subqueries in FROM clause and subqueries in WHERE +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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); +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (1000)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE ((f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second)) AND (f2.id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (1000)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE ((f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second)) AND (f2.id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (1000)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE ((f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second)) AND (f2.id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id) SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first, public.reference_table_13300012 reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (1000)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE ((f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second)) AND (f2.id IS NOT NULL)) +-- some UPSERTS +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; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) +-- upserts 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; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS ae (user_id, value_1_agg, agg_time) SELECT user_id, value_1, "time" FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) ON CONFLICT(user_id, value_1_agg) DO UPDATE SET agg_time = excluded.agg_time WHERE (ae.agg_time OPERATOR(pg_catalog.<) excluded.agg_time) RETURNING ae.user_id, ae.value_1_agg + user_id | value_1_agg +--------------------------------------------------------------------- + 7 | +(1 row) + +INSERT INTO agg_events (user_id, value_1_agg) +SELECT + user_id, sum(value_1 + value_2) +FROM + raw_events_first GROUP BY user_id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) AS sum FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +-- FILTER CLAUSE +INSERT INTO agg_events (user_id, value_1_agg) +SELECT + user_id, sum(value_1 + value_2) FILTER (where value_3 = 15) +FROM + raw_events_first GROUP BY user_id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT user_id, sum((value_1 OPERATOR(pg_catalog.+) value_2)) FILTER (WHERE (value_3 OPERATOR(pg_catalog.=) (15)::double precision)) AS sum FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) GROUP BY user_id +-- a test with reference table JOINs +INSERT INTO + agg_events (user_id, value_1_agg) +SELECT + raw_events_first.user_id, sum(value_1) +FROM + reference_table, raw_events_first +WHERE + raw_events_first.user_id = reference_table.user_id +GROUP BY + raw_events_first.user_id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum FROM public.reference_table_13300012 reference_table, public.raw_events_first_13300000 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id) AND (raw_events_first.user_id IS NOT NULL)) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum FROM public.reference_table_13300012 reference_table, public.raw_events_first_13300001 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id) AND (raw_events_first.user_id IS NOT NULL)) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum FROM public.reference_table_13300012 reference_table, public.raw_events_first_13300002 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id) AND (raw_events_first.user_id IS NOT NULL)) GROUP BY raw_events_first.user_id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT raw_events_first.user_id, sum(raw_events_first.value_1) AS sum FROM public.reference_table_13300012 reference_table, public.raw_events_first_13300003 raw_events_first WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id) AND (raw_events_first.user_id IS NOT NULL)) GROUP BY raw_events_first.user_id +-- a note on the outer joins is that +-- we filter out outer join results +-- where partition column returns +-- NULL. Thus, we could INSERT less rows +-- than we expect from subquery result. +-- see the following tests +SET client_min_messages TO INFO; +-- we don't want to see constraint violations, so truncate first +TRUNCATE agg_events; +-- add a row to first table to make table contents different +INSERT INTO raw_events_second (user_id, time, value_1, value_2, value_3, value_4) VALUES + (10, now(), 100, 10000, 10000, 100000); +DELETE FROM raw_events_second WHERE user_id = 2; +-- we select 11 rows +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 + ORDER BY t1.user_id, + t2.user_id; + col1 | col2 +--------------------------------------------------------------------- + 1 | 1 + 2 | + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 + | 10 +(10 rows) + +SET client_min_messages TO DEBUG2; +-- we insert 10 rows since we filtered out +-- NULL partition column values +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; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT t1.user_id AS col1, t2.user_id AS col2 FROM (public.raw_events_first_13300000 t1 FULL JOIN public.raw_events_second_13300004 t2 ON ((t1.user_id OPERATOR(pg_catalog.=) t2.user_id))) WHERE (t1.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT t1.user_id AS col1, t2.user_id AS col2 FROM (public.raw_events_first_13300001 t1 FULL JOIN public.raw_events_second_13300005 t2 ON ((t1.user_id OPERATOR(pg_catalog.=) t2.user_id))) WHERE (t1.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT t1.user_id AS col1, t2.user_id AS col2 FROM (public.raw_events_first_13300002 t1 FULL JOIN public.raw_events_second_13300006 t2 ON ((t1.user_id OPERATOR(pg_catalog.=) t2.user_id))) WHERE (t1.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT t1.user_id AS col1, t2.user_id AS col2 FROM (public.raw_events_first_13300003 t1 FULL JOIN public.raw_events_second_13300007 t2 ON ((t1.user_id OPERATOR(pg_catalog.=) t2.user_id))) WHERE (t1.user_id IS NOT NULL) +SET client_min_messages TO INFO; +-- see that the results are different from the SELECT query +SELECT + user_id, value_1_agg +FROM + agg_events +ORDER BY + user_id, value_1_agg; + user_id | value_1_agg +--------------------------------------------------------------------- + 1 | 1 + 2 | + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 +(9 rows) + +-- we don't want to see constraint violations, so truncate first +SET client_min_messages TO INFO; +TRUNCATE agg_events; +SET client_min_messages TO DEBUG2; +-- DISTINCT clause +INSERT INTO agg_events (value_1_agg, user_id) + SELECT + DISTINCT value_1, user_id + FROM + raw_events_first; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT user_id, value_1 FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) +-- we don't want to see constraint violations, so truncate first +SET client_min_messages TO INFO; +truncate agg_events; +SET client_min_messages TO DEBUG2; +-- DISTINCT ON clauses are supported +-- distinct on(non-partition column) +-- values are pulled to master +INSERT INTO agg_events (value_1_agg, user_id) + SELECT + 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: 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; +DEBUG: Router planner cannot handle multi-shard select queries + user_id | value_1_agg +--------------------------------------------------------------------- + 1 | 10 + 2 | 20 + 3 | 30 + 4 | 40 + 5 | 50 + 6 | 60 + 7 | + 8 | 80 + 9 | 90 +(9 rows) + +-- we don't want to see constraint violations, so truncate first +SET client_min_messages TO INFO; +truncate agg_events; +SET client_min_messages TO DEBUG2; +-- distinct on(partition column) +-- queries are forwared to workers +INSERT INTO agg_events (value_1_agg, user_id) + SELECT + DISTINCT ON (user_id) value_1, user_id + FROM + raw_events_first; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300000 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300001 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300002 raw_events_first WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_1_agg) SELECT DISTINCT ON (user_id) user_id, value_1 FROM public.raw_events_first_13300003 raw_events_first WHERE (user_id IS NOT NULL) +SELECT user_id, value_1_agg FROM agg_events ORDER BY 1,2; +DEBUG: Router planner cannot handle multi-shard select queries + user_id | value_1_agg +--------------------------------------------------------------------- + 1 | 10 + 2 | 20 + 3 | 30 + 4 | 40 + 5 | 50 + 6 | 60 + 7 | + 8 | 80 + 9 | 90 +(9 rows) + +-- We support CTEs +BEGIN; +WITH fist_table_agg 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 + fist_table_agg; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_1 for CTE fist_table_agg: SELECT (max(value_1) OPERATOR(pg_catalog.+) 1) AS v1_agg, user_id FROM public.raw_events_first GROUP BY user_id +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, v1_agg AS value_1_agg FROM (SELECT fist_table_agg.user_id, fist_table_agg.v1_agg FROM (SELECT intermediate_result.v1_agg, intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(v1_agg integer, user_id integer)) fist_table_agg) citus_insert_select_subquery +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +ROLLBACK; +-- We don't support CTEs that are referenced in the target list +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 +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 public.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 public.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 public.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 public.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 +BEGIN; +INSERT INTO + raw_events_first(user_id) +SELECT + user_id +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 public.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 public.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 public.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 public.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) +ROLLBACK; +-- We do support set operations through recursive planning +BEGIN; +SET LOCAL client_min_messages TO DEBUG; +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: 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 public.raw_events_first +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: generating subplan XXX_2 for subquery SELECT user_id FROM public.raw_events_first +DEBUG: Creating router plan +DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) INTERSECT SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) citus_insert_select_subquery +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +ROLLBACK; +-- If the query is router plannable then it is executed via the coordinator +INSERT INTO + raw_events_first(user_id) +SELECT + user_id +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: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- some supported LEFT joins + 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; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300000 raw_events_first LEFT JOIN public.raw_events_second_13300004 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300001 raw_events_first LEFT JOIN public.raw_events_second_13300005 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300002 raw_events_first LEFT JOIN public.raw_events_second_13300006 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_first.user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300003 raw_events_first LEFT JOIN public.raw_events_second_13300007 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (raw_events_first.user_id IS NOT NULL) + INSERT INTO agg_events (user_id) + SELECT + 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 public.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (public.reference_table_13300012 reference_table LEFT JOIN public.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 public.agg_events_13300009 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (public.reference_table_13300012 reference_table LEFT JOIN public.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 public.agg_events_13300010 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (public.reference_table_13300012 reference_table LEFT JOIN public.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 public.agg_events_13300011 AS citus_table_alias (user_id) SELECT raw_events_second.user_id FROM (public.reference_table_13300012 reference_table LEFT JOIN public.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) + 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_first.user_id = 10; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300000 raw_events_first LEFT JOIN public.raw_events_second_13300004 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) 10) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: Skipping target shard interval 13300009 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300010 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300011 since SELECT query for it pruned away + 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; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300000 raw_events_first LEFT JOIN public.raw_events_second_13300004 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (((raw_events_second.user_id OPERATOR(pg_catalog.=) 10) OR (raw_events_second.user_id OPERATOR(pg_catalog.=) 11)) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300001 raw_events_first LEFT JOIN (SELECT NULL::integer AS 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 WHERE false) raw_events_second(user_id, "time", value_1, value_2, value_3, value_4) ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (((raw_events_second.user_id OPERATOR(pg_catalog.=) 10) OR (raw_events_second.user_id OPERATOR(pg_catalog.=) 11)) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300002 raw_events_first LEFT JOIN (SELECT NULL::integer AS 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 WHERE false) raw_events_second(user_id, "time", value_1, value_2, value_3, value_4) ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (((raw_events_second.user_id OPERATOR(pg_catalog.=) 10) OR (raw_events_second.user_id OPERATOR(pg_catalog.=) 11)) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300003 raw_events_first LEFT JOIN public.raw_events_second_13300007 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE (((raw_events_second.user_id OPERATOR(pg_catalog.=) 10) OR (raw_events_second.user_id OPERATOR(pg_catalog.=) 11)) AND (raw_events_first.user_id IS NOT NULL)) + 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.user_id + WHERE raw_events_first.user_id = 10 AND raw_events_first.user_id = 20; +DEBUG: Skipping target shard interval 13300008 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300009 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300010 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300011 since SELECT query for it pruned away + 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_first.user_id = 10 AND raw_events_second.user_id = 20; +DEBUG: Skipping target shard interval 13300008 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300009 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300010 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300011 since SELECT query for it pruned away + 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_first.user_id IN (19, 20, 21); +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300000 raw_events_first LEFT JOIN public.raw_events_second_13300004 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300001 raw_events_first LEFT JOIN public.raw_events_second_13300005 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300002 raw_events_first LEFT JOIN public.raw_events_second_13300006 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM ((SELECT NULL::integer AS 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 WHERE false) raw_events_first(user_id, "time", value_1, value_2, value_3, value_4) LEFT JOIN public.raw_events_second_13300007 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_first.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) + 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.user_id + WHERE raw_events_second.user_id IN (19, 20, 21); +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300000 raw_events_first JOIN public.raw_events_second_13300004 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300001 raw_events_first JOIN public.raw_events_second_13300005 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300002 raw_events_first JOIN public.raw_events_second_13300006 raw_events_second ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id) SELECT raw_events_first.user_id FROM (public.raw_events_first_13300003 raw_events_first JOIN (SELECT NULL::integer AS 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 WHERE false) raw_events_second(user_id, "time", value_1, value_2, value_3, value_4) ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id))) WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) ANY (ARRAY[19, 20, 21])) AND (raw_events_first.user_id IS NOT NULL)) +SET client_min_messages TO WARNING; + -- following query should use repartitioned joins and results should + -- be routed via coordinator + SET citus.enable_repartition_joins TO true; + INSERT INTO agg_events + (user_id) + SELECT raw_events_first.user_id + FROM raw_events_first, + raw_events_second + WHERE raw_events_second.user_id = raw_events_first.value_1 + AND raw_events_first.value_1 = 12; + -- some unsupported LEFT/INNER JOINs + -- JOIN on one table with partition column other is not + 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.value_1; +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns + -- same as the above with 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; + -- a not meaningful query + INSERT INTO agg_events + (user_id) + SELECT raw_events_second.user_id + FROM raw_events_first, + raw_events_second + WHERE raw_events_first.user_id = raw_events_first.value_1; +ERROR: cannot perform distributed planning on this query +DETAIL: Cartesian products are currently unsupported + -- both tables joined on non-partition columns + 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.value_1 = raw_events_second.value_1; +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns + -- same as the above with INNER JOIN + -- we support this with route to coordinator + SELECT coordinator_plan($Q$ + EXPLAIN (costs off) + 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.value_1 = raw_events_second.value_1; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + +-- EXPLAIN ANALYZE is not supported for INSERT ... SELECT via coordinator +EXPLAIN (costs off, analyze on) + 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.value_1 = raw_events_second.value_1; +ERROR: EXPLAIN ANALYZE is currently not supported for INSERT ... SELECT commands via coordinator +-- even if there is a filter on the partition key, since the join is not on the partition key we reject +-- this query +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.value_1 +WHERE + raw_events_first.user_id = 10; +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns + -- same as the above with INNER JOIN + -- we support this with route to coordinator + SELECT coordinator_plan($Q$ + EXPLAIN (costs off) + 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.user_id = 10; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + + -- make things a bit more complicate with IN clauses + -- we support this with route to coordinator + SELECT coordinator_plan($Q$ + EXPLAIN (costs off) + 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); +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + + -- implicit join on non partition column should also not be pushed down, + -- so we fall back to route via coordinator + SELECT coordinator_plan($Q$ + EXPLAIN (costs off) + INSERT INTO agg_events + (user_id) + SELECT raw_events_first.user_id + FROM raw_events_first, + raw_events_second + WHERE raw_events_second.user_id = raw_events_first.value_1; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + +RESET client_min_messages; + -- The following is again a tricky query for Citus. If the given filter was + -- on value_1 as shown in the above, Citus could push it down and use + -- distributed INSERT/SELECT. But we instead fall back to route via coordinator. + SELECT coordinator_plan($Q$ + EXPLAIN (costs off) + INSERT INTO agg_events + (user_id) + SELECT raw_events_first.user_id + FROM raw_events_first, + raw_events_second + WHERE raw_events_second.user_id = raw_events_first.value_1 + AND raw_events_first.value_2 = 12; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + + -- foo is not joined on the partition key so the query is not + -- pushed down. So instead we route via coordinator. + SELECT coordinator_plan($Q$ + EXPLAIN (costs off) + INSERT INTO agg_events + (user_id, value_4_agg) + SELECT + outer_most.id, max(outer_most.value) + FROM + ( + SELECT f2.id as id, f2.v4 as value FROM + (SELECT + id + FROM (SELECT reference_table.user_id AS id + FROM raw_events_first LEFT JOIN + reference_table + ON (raw_events_first.value_1 = reference_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) > 10) AS foo2 ) as f2 + ON (f.id = f2.id)) as outer_most + GROUP BY + outer_most.id; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> HashAggregate + Group Key: remote_scan.user_id + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(8 rows) + + -- if the given filter was on value_1 as shown in the above, Citus could + -- push it down. But here the query falls back to route via coordinator. + SELECT coordinator_plan($Q$ + EXPLAIN (costs off) + INSERT INTO agg_events + (user_id) + SELECT raw_events_first.user_id + FROM raw_events_first, + raw_events_second + WHERE raw_events_second.user_id = raw_events_first.value_1 + AND raw_events_first.value_2 = 12; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + + -- foo is not joined on the partition key so the query is not + -- pushed down, and it falls back to route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) + INSERT INTO agg_events + (user_id, value_4_agg) + SELECT + outer_most.id, max(outer_most.value) + FROM + ( + SELECT f2.id as id, f2.v4 as value FROM + (SELECT + id + FROM (SELECT reference_table.user_id AS id + FROM raw_events_first LEFT JOIN + reference_table + ON (raw_events_first.value_1 = reference_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) > 10) AS foo2 ) as f2 + ON (f.id = f2.id)) as outer_most + GROUP BY + outer_most.id; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> HashAggregate + Group Key: remote_scan.user_id + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(8 rows) + +INSERT INTO agg_events + (value_4_agg, + value_1_agg, + user_id) +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) AS foo; +ERROR: complex joins are only supported when all distributed tables are joined on their distribution columns with equal operator +SET client_min_messages TO DEBUG2; +-- INSERT returns NULL partition key value via coordinator +INSERT INTO agg_events + (value_4_agg, + value_1_agg, + user_id) +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.value_3 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.value_3) AS foo; +DEBUG: Group by list without distribution column is not allowed in distributed INSERT ... SELECT queries +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] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [0,1073741823] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [1073741824,2147483647] +DEBUG: join prunable for intervals [-1073741824,-1] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [-1073741824,-1] and [0,1073741823] +DEBUG: join prunable for intervals [-1073741824,-1] and [1073741824,2147483647] +DEBUG: join prunable for intervals [0,1073741823] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [0,1073741823] and [-1073741824,-1] +DEBUG: join prunable for intervals [0,1073741823] and [1073741824,2147483647] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-1073741824,-1] +DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] +DEBUG: generating subplan XXX_1 for subquery SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first.value_1) AS v1, raw_events_second.value_3 AS id FROM public.raw_events_first, public.raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.value_3 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT int4(id) AS user_id, int4(v1) AS value_1_agg, int8(v4) AS value_4_agg FROM (SELECT intermediate_result.v4, intermediate_result.v1, intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(v4 numeric, v1 bigint, id double precision)) foo +DEBUG: Creating router plan +DEBUG: Collecting INSERT ... SELECT results on coordinator +ERROR: the partition column of table public.agg_events cannot be NULL +-- error cases +-- no part column at all +INSERT INTO raw_events_second + (value_1) +SELECT value_1 +FROM raw_events_first; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: the query doesn't include the target table's partition column +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +ERROR: the partition column of table public.raw_events_second should have a value +INSERT INTO raw_events_second + (value_1) +SELECT user_id +FROM raw_events_first; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: the query doesn't include the target table's partition column +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +ERROR: the partition column of table public.raw_events_second should have a value +INSERT INTO raw_events_second + (user_id) +SELECT value_1 +FROM raw_events_first; +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 cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'user_id' +ERROR: the partition column value cannot be NULL +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO raw_events_second + (user_id) +SELECT user_id * 2 +FROM raw_events_first; +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: 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 public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0,repartitioned_results_xxxxx_from_13300001_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_1,repartitioned_results_xxxxx_from_13300001_to_1,repartitioned_results_xxxxx_from_13300003_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_3,repartitioned_results_xxxxx_from_13300002_to_3,repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +INSERT INTO raw_events_second + (user_id) +SELECT user_id :: bigint +FROM raw_events_first; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains an explicit cast 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: 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 public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300000_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300001_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300002_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM read_intermediate_results('{repartitioned_results_xxxxx_from_13300003_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(user_id integer) +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; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains an aggregation 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: 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' +ERROR: the partition column value cannot be NULL +CONTEXT: while executing command on localhost:xxxxx +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), + value_2 +FROM raw_events_first +GROUP BY user_id, + value_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: 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' +ERROR: the partition column value cannot be NULL +CONTEXT: while executing command on localhost:xxxxx +-- tables should be co-located +INSERT INTO agg_events (user_id) +SELECT + user_id +FROM + reference_table; +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 +-- foo2 is recursively planned and INSERT...SELECT is done via coordinator +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_table.user_id ) AS foo) as f +INNER JOIN +(SELECT v4, + v1, + id +FROM (SELECT SUM(raw_events_second.value_4) AS v4, + raw_events_second.value_1 AS v1, + SUM(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.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: 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] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [0,1073741823] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [1073741824,2147483647] +DEBUG: join prunable for intervals [-1073741824,-1] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [-1073741824,-1] and [0,1073741823] +DEBUG: join prunable for intervals [-1073741824,-1] and [1073741824,2147483647] +DEBUG: join prunable for intervals [0,1073741823] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [0,1073741823] and [-1073741824,-1] +DEBUG: join prunable for intervals [0,1073741823] and [1073741824,2147483647] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-1073741824,-1] +DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] +DEBUG: generating subplan XXX_1 for subquery SELECT sum(raw_events_second.value_4) AS v4, raw_events_second.value_1 AS v1, sum(raw_events_second.user_id) AS id FROM public.raw_events_first, public.raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.value_1 HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT int4(f2.id) AS user_id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first, public.reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT intermediate_result.v4, intermediate_result.v1, intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(v4 numeric, v1 integer, id bigint)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) +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' +-- the second part of the query is not routable since +-- GROUP BY not on the partition column (i.e., value_1) and thus join +-- on f.id = f2.id is not on the partition key (instead on the sum of partition key) +-- but we still recursively plan foo2 and run the query +INSERT INTO agg_events + (user_id) +SELECT f.id FROM +(SELECT + id +FROM (SELECT raw_events_first.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_table.user_id ) AS foo) as f +INNER JOIN +(SELECT v4, + v1, + id +FROM (SELECT SUM(raw_events_second.value_4) AS v4, + raw_events_second.value_1 AS v1, + SUM(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.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: 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] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [0,1073741823] +DEBUG: join prunable for intervals [-2147483648,-1073741825] and [1073741824,2147483647] +DEBUG: join prunable for intervals [-1073741824,-1] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [-1073741824,-1] and [0,1073741823] +DEBUG: join prunable for intervals [-1073741824,-1] and [1073741824,2147483647] +DEBUG: join prunable for intervals [0,1073741823] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [0,1073741823] and [-1073741824,-1] +DEBUG: join prunable for intervals [0,1073741823] and [1073741824,2147483647] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-2147483648,-1073741825] +DEBUG: join prunable for intervals [1073741824,2147483647] and [-1073741824,-1] +DEBUG: join prunable for intervals [1073741824,2147483647] and [0,1073741823] +DEBUG: generating subplan XXX_1 for subquery SELECT sum(raw_events_second.value_4) AS v4, raw_events_second.value_1 AS v1, sum(raw_events_second.user_id) AS id FROM public.raw_events_first, public.raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.value_1 HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT f.id AS user_id FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM public.raw_events_first, public.reference_table WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT intermediate_result.v4, intermediate_result.v1, intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(v4 numeric, v1 integer, id bigint)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) +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' +SET client_min_messages TO WARNING; +-- cannot pushdown the query since the JOIN is not equi JOIN +-- falls back to route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO agg_events + (user_id, value_4_agg) +SELECT +outer_most.id, max(outer_most.value) + FROM +( + SELECT f2.id as id, f2.v4 as value FROM + (SELECT + id + FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id != f2.id)) as outer_most +GROUP BY outer_most.id; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> HashAggregate + Group Key: remote_scan.user_id + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(8 rows) + +-- cannot pushdown since foo2 is not join on partition key +-- falls back to route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO agg_events + (user_id, value_4_agg) +SELECT + outer_most.id, max(outer_most.value) +FROM +( + SELECT f2.id as id, f2.v4 as value FROM + (SELECT + id + FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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.value_1 + GROUP BY raw_events_second.user_id + HAVING SUM(raw_events_second.value_4) > 10) AS foo2 ) as f2 +ON (f.id = f2.id)) as outer_most +GROUP BY + outer_most.id; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> HashAggregate + Group Key: remote_scan.user_id + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> HashAggregate + Group Key: remote_scan.id + Filter: (pg_catalog.sum(remote_scan.worker_column_4) > '10'::numeric) + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(11 rows) + +-- cannot push down since foo doesn't have en equi join +-- falls back to route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO agg_events + (user_id, value_4_agg) +SELECT + outer_most.id, max(outer_most.value) +FROM +( + SELECT f2.id as id, f2.v4 as value FROM + (SELECT + id + FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id != reference_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) > 10) AS foo2 ) as f2 +ON (f.id = f2.id)) as outer_most +GROUP BY + outer_most.id; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> HashAggregate + Group Key: remote_scan.user_id + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(8 rows) + +-- some unsupported LATERAL JOINs +-- join on averages is not on the partition key +-- should fall back to route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO agg_events (user_id, value_4_agg) +SELECT + averages.user_id, avg(averages.value_4) +FROM + (SELECT + raw_events_second.user_id + FROM + reference_table JOIN raw_events_second on (reference_table.user_id = raw_events_second.user_id) + ) reference_ids + JOIN LATERAL + (SELECT + user_id, value_4 + FROM + raw_events_first WHERE + value_4 = reference_ids.user_id) as averages ON true + GROUP BY averages.user_id; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> HashAggregate + Group Key: remote_scan.user_id + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +-- join among reference_ids and averages is not on the partition key +-- should fall back to route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO agg_events (user_id, value_4_agg) +SELECT + averages.user_id, avg(averages.value_4) +FROM + (SELECT + raw_events_second.user_id + FROM + reference_table JOIN raw_events_second on (reference_table.user_id = raw_events_second.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; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> HashAggregate + Group Key: remote_scan.user_id + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(8 rows) + +-- join among the agg_ids and averages is not on the partition key +-- should fall back to route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO agg_events (user_id, value_4_agg) +SELECT + averages.user_id, avg(averages.value_4) +FROM + (SELECT + raw_events_second.user_id + FROM + reference_table JOIN raw_events_second on (reference_table.user_id = raw_events_second.user_id) + ) reference_ids + JOIN LATERAL + (SELECT + user_id, value_4 + FROM + raw_events_first) as averages ON averages.user_id = reference_ids.user_id +JOIN LATERAL + (SELECT user_id, value_4 FROM agg_events) as agg_ids ON (agg_ids.value_4 = averages.user_id) + GROUP BY averages.user_id; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + +-- Selected value in the WHERE is not partition key, so we cannot use distributed +-- INSERT/SELECT and falls back route via coordinator +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN (SELECT value_1 + FROM raw_events_second); +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +-- same as above but slightly more complex +-- since it also includes subquery in FROM as well +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO agg_events + (user_id) +SELECT f2.id FROM + +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT value_1 + FROM raw_events_second); +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +-- some more semi-anti join tests +SET client_min_messages TO DEBUG2; +-- join in where +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); +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second, public.raw_events_first_13300000 raw_events_first_1 WHERE ((raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first_1.user_id) AND (raw_events_first_1.user_id OPERATOR(pg_catalog.=) 200)))) AND (user_id IS NOT NULL)) +DEBUG: Skipping target shard interval 13300005 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300006 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300007 since SELECT query for it pruned away +RESET client_min_messages; +-- we cannot push this down since it is NOT IN +-- we use repartition insert/select instead +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id NOT 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); +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 1 +(6 rows) + +SET client_min_messages TO DEBUG2; +-- safe to push down +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE EXISTS (SELECT 1 + FROM raw_events_second + WHERE raw_events_second.user_id =raw_events_first.user_id); +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((EXISTS (SELECT 1 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id))) AND (user_id IS NOT NULL)) +-- we cannot push down +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); +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300004 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300005 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300006 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((NOT (EXISTS (SELECT 1 FROM public.raw_events_second_13300007 raw_events_second WHERE (raw_events_second.user_id OPERATOR(pg_catalog.=) raw_events_first.user_id)))) AND (user_id IS NOT NULL)) +-- more complex LEFT JOINs + INSERT INTO agg_events + (user_id, value_4_agg) + SELECT + outer_most.id, max(outer_most.value) + FROM + ( + SELECT f2.id as id, f2.v4 as value FROM + (SELECT + id + FROM (SELECT raw_events_first.user_id AS id + FROM raw_events_first LEFT JOIN + reference_table + ON (raw_events_first.user_id = reference_table.user_id)) AS foo) as f + LEFT 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) > 10) AS foo2 ) as f2 + ON (f.id = f2.id)) as outer_most + GROUP BY + outer_most.id; +DEBUG: distributed statement: INSERT INTO public.agg_events_13300008 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300000 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300000 raw_events_first, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300009 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300001 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300001 raw_events_first, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300010 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300002 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300002 raw_events_first, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +DEBUG: distributed statement: INSERT INTO public.agg_events_13300011 AS citus_table_alias (user_id, value_4_agg) SELECT id, max(value) AS max FROM (SELECT f2.id, f2.v4 AS value FROM ((SELECT foo.id FROM (SELECT raw_events_first.user_id AS id FROM (public.raw_events_first_13300003 raw_events_first LEFT JOIN public.reference_table_13300012 reference_table ON ((raw_events_first.user_id OPERATOR(pg_catalog.=) reference_table.user_id)))) foo) f LEFT JOIN (SELECT foo2.v4, foo2.v1, foo2.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 public.raw_events_first_13300003 raw_events_first, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id)))) outer_most WHERE (id IS NOT NULL) GROUP BY id +RESET client_min_messages; +-- cannot push down since the f.id IN is matched with value_1 +-- we use repartition insert/select instead +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN ( +SELECT f2.id FROM +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT value_1 + FROM raw_events_second)); +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + -> Distributed Subplan XXX_1 + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(6 rows) + +SET client_min_messages TO DEBUG2; +-- same as above, but this time is it safe to push down since +-- f.id IN is matched with user_id +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN ( +SELECT f2.id FROM +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT user_id + FROM raw_events_second)); +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300004 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300000 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300000 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300000 raw_events_first_1, public.raw_events_second_13300004 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300004 raw_events_second)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300005 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300001 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300001 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300001 raw_events_first_1, public.raw_events_second_13300005 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300005 raw_events_second)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300006 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300002 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300002 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300002 raw_events_first_1, public.raw_events_second_13300006 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300006 raw_events_second)))) AND (user_id IS NOT NULL)) +DEBUG: distributed statement: INSERT INTO public.raw_events_second_13300007 AS citus_table_alias (user_id) SELECT user_id FROM public.raw_events_first_13300003 raw_events_first WHERE ((user_id OPERATOR(pg_catalog.=) ANY (SELECT f2.id FROM ((SELECT foo.id FROM (SELECT reference_table.user_id AS id FROM public.raw_events_first_13300003 raw_events_first_1, public.reference_table_13300012 reference_table WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) reference_table.user_id)) foo) f JOIN (SELECT foo2.v4, foo2.v1, foo2.id FROM (SELECT sum(raw_events_second.value_4) AS v4, sum(raw_events_first_1.value_1) AS v1, raw_events_second.user_id AS id FROM public.raw_events_first_13300003 raw_events_first_1, public.raw_events_second_13300007 raw_events_second WHERE (raw_events_first_1.user_id OPERATOR(pg_catalog.=) raw_events_second.user_id) GROUP BY raw_events_second.user_id HAVING (sum(raw_events_second.value_4) OPERATOR(pg_catalog.>) (10)::numeric)) foo2) f2 ON ((f.id OPERATOR(pg_catalog.=) f2.id))) WHERE (f.id OPERATOR(pg_catalog.=) ANY (SELECT raw_events_second.user_id FROM public.raw_events_second_13300007 raw_events_second)))) AND (user_id IS NOT NULL)) +RESET client_min_messages; +-- cannot push down since top level user_id is matched with NOT IN +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id NOT IN ( +SELECT f2.id FROM +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id = f2.id) +WHERE f.id IN (SELECT user_id + FROM raw_events_second)); +ERROR: cannot pushdown the subquery +DETAIL: There exist a reference table in the outer part of the outer join +-- cannot push down since join is not equi join (f.id > f2.id) +INSERT INTO raw_events_second + (user_id) +SELECT user_id +FROM raw_events_first +WHERE user_id IN ( +SELECT f2.id FROM +(SELECT + id +FROM (SELECT reference_table.user_id AS id + FROM raw_events_first, + reference_table + WHERE raw_events_first.user_id = reference_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) > 10) AS foo2 ) as f2 +ON (f.id > f2.id) +WHERE f.id IN (SELECT user_id + FROM raw_events_second)); +ERROR: cannot pushdown the subquery +DETAIL: There exist a reference table in the outer part of the outer join +-- we currently not support grouping sets +INSERT INTO agg_events + (user_id, + value_1_agg, + value_2_agg) +SELECT user_id, + Sum(value_1) AS sum_val1, + Sum(value_2) AS sum_val2 +FROM raw_events_second +GROUP BY grouping sets ( ( user_id ), ( value_1 ), ( user_id, value_1 ), ( ) ); +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. +-- set back to INFO +SET client_min_messages TO INFO; +-- avoid constraint violations +TRUNCATE raw_events_first; +-- we don't support LIMIT for subquery pushdown, but +-- we recursively plan the query and run it via coordinator +INSERT INTO agg_events(user_id) +SELECT user_id +FROM users_table +WHERE user_id + IN (SELECT + user_id + FROM ( + ( + SELECT + user_id + FROM + ( + SELECT + e1.user_id + FROM + users_table u1, events_table e1 + WHERE + e1.user_id = u1.user_id LIMIT 3 + ) as f_inner + ) + ) AS f2); +-- Altering a table and selecting from it using a multi-shard statement +-- in the same transaction is allowed because we will use the same +-- connections for all co-located placements. +BEGIN; +ALTER TABLE raw_events_second DROP COLUMN value_4; +INSERT INTO raw_events_first SELECT * FROM raw_events_second; +ROLLBACK; +-- Alterating a table and selecting from it using a single-shard statement +-- in the same transaction is disallowed because we will use a different +-- connection. +BEGIN; +ALTER TABLE raw_events_second DROP COLUMN value_4; +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 100; +ROLLBACK; +-- Altering a reference table and then performing an INSERT ... SELECT which +-- joins with the reference table is allowed, since the INSERT ... SELECT +-- would read from the reference table over the same connections with the ones +-- that performed the parallel DDL. +BEGIN; +ALTER TABLE reference_table ADD COLUMN z int; +INSERT INTO raw_events_first (user_id) +SELECT user_id FROM raw_events_second JOIN reference_table USING (user_id); +ROLLBACK; +-- the same test with sequential DDL should work fine +BEGIN; +SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; +ALTER TABLE reference_table ADD COLUMN z int; +INSERT INTO raw_events_first (user_id) +SELECT user_id FROM raw_events_second JOIN reference_table USING (user_id); +ROLLBACK; +-- Insert after copy is allowed +BEGIN; +COPY raw_events_second (user_id, value_1) FROM STDIN DELIMITER ','; +INSERT INTO raw_events_first SELECT * FROM raw_events_second; +ROLLBACK; +-- Insert after copy is currently allowed for single-shard operation. +-- Both insert and copy are rolled back successfully. +BEGIN; +COPY raw_events_second (user_id, value_1) FROM STDIN DELIMITER ','; +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 101; +SELECT user_id FROM raw_events_first WHERE user_id = 101; + user_id +--------------------------------------------------------------------- + 101 +(1 row) + +ROLLBACK; +BEGIN; +INSERT INTO raw_events_first SELECT * FROM raw_events_second; +COPY raw_events_first (user_id, value_1) FROM STDIN DELIMITER ','; +ROLLBACK; +BEGIN; +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 100; +COPY raw_events_first (user_id, value_1) FROM STDIN DELIMITER ','; +ROLLBACK; +-- Similarly, multi-row INSERTs will take part in transactions and reuse connections... +BEGIN; +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 100; +COPY raw_events_first (user_id, value_1) FROM STDIN DELIMITER ','; +INSERT INTO raw_events_first (user_id, value_1) VALUES (105, 105), (106, 106); +ROLLBACK; +-- selecting from views works +CREATE VIEW test_view AS SELECT * FROM raw_events_first; +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (16, now(), 60, 600, 6000.1, 60000); +SELECT count(*) FROM raw_events_second; + count +--------------------------------------------------------------------- + 36 +(1 row) + +INSERT INTO raw_events_second SELECT * FROM test_view; +INSERT INTO raw_events_first (user_id, time, value_1, value_2, value_3, value_4) VALUES + (17, now(), 60, 600, 6000.1, 60000); +INSERT INTO raw_events_second SELECT * FROM test_view WHERE user_id = 17 GROUP BY 1,2,3,4,5,6; +SELECT count(*) FROM raw_events_second; + count +--------------------------------------------------------------------- + 38 +(1 row) + +-- intermediate results (CTEs) should be allowed when doing INSERT...SELECT within a CTE +WITH series AS ( + SELECT s AS val FROM generate_series(60,70) s +), +inserts AS ( + INSERT INTO raw_events_second (user_id) + SELECT + user_id + FROM + raw_events_first JOIN series ON (value_1 = val) + RETURNING + NULL +) +SELECT count(*) FROM inserts; + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- we need this in our next test +truncate raw_events_first; +SET client_min_messages TO DEBUG2; +-- first show that the query works now +INSERT INTO raw_events_first SELECT * FROM raw_events_second; +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300005 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300007 raw_events_second WHERE (user_id IS NOT NULL) +SET client_min_messages TO INFO; +truncate raw_events_first; +SET client_min_messages TO DEBUG2; +-- now show that it works for a single shard query as well +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 5; +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE ((user_id OPERATOR(pg_catalog.=) 5) AND (user_id IS NOT NULL)) +DEBUG: Skipping target shard interval 13300001 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300002 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300003 since SELECT query for it pruned away +SET client_min_messages TO INFO; +-- if a single shard of the SELECT is unhealty, the query should fail +UPDATE pg_dist_shard_placement SET shardstate = 3 WHERE shardid = 13300004 AND nodeport = :worker_1_port; +truncate raw_events_first; +SET client_min_messages TO DEBUG2; +-- this should fail +INSERT INTO raw_events_first SELECT * FROM raw_events_second; +ERROR: cannot perform distributed planning for the given modification +DETAIL: Insert query cannot be executed on all placements for shard xxxxx +-- this should also fail +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 5; +ERROR: cannot perform distributed planning for the given modification +DETAIL: Insert query cannot be executed on all placements for shard xxxxx +-- but this should work given that it hits different shard +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 6; +DEBUG: Skipping target shard interval 13300000 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300001 since SELECT query for it pruned away +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE ((user_id OPERATOR(pg_catalog.=) 6) AND (user_id IS NOT NULL)) +DEBUG: Skipping target shard interval 13300003 since SELECT query for it pruned away +SET client_min_messages TO INFO; +-- mark the unhealthy placement as healthy again for the next tests +UPDATE pg_dist_shard_placement SET shardstate = 1 WHERE shardid = 13300004 AND nodeport = :worker_1_port; +-- now that we should show that it works if one of the target shard interval is not healthy +UPDATE pg_dist_shard_placement SET shardstate = 3 WHERE shardid = 13300000 AND nodeport = :worker_1_port; +truncate raw_events_first; +SET client_min_messages TO DEBUG2; +-- this should work +INSERT INTO raw_events_first SELECT * FROM raw_events_second; +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300005 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300006 raw_events_second WHERE (user_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300007 raw_events_second WHERE (user_id IS NOT NULL) +SET client_min_messages TO INFO; +truncate raw_events_first; +SET client_min_messages TO DEBUG2; +-- this should also work +INSERT INTO raw_events_first SELECT * FROM raw_events_second WHERE user_id = 5; +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, "time", value_1, value_2, value_3, value_4) SELECT user_id, "time", value_1, value_2, value_3, value_4 FROM public.raw_events_second_13300004 raw_events_second WHERE ((user_id OPERATOR(pg_catalog.=) 5) AND (user_id IS NOT NULL)) +DEBUG: Skipping target shard interval 13300001 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300002 since SELECT query for it pruned away +DEBUG: Skipping target shard interval 13300003 since SELECT query for it pruned away +SET client_min_messages TO INFO; +-- now do some tests with varchars +INSERT INTO insert_select_varchar_test VALUES ('test_1', 10); +INSERT INTO insert_select_varchar_test VALUES ('test_2', 30); +INSERT INTO insert_select_varchar_test (key, value) +SELECT *, 100 +FROM (SELECT f1.key + FROM (SELECT key + FROM insert_select_varchar_test + GROUP BY 1 + HAVING Count(key) < 3) AS f1, + (SELECT key + FROM insert_select_varchar_test + GROUP BY 1 + HAVING Sum(COALESCE(insert_select_varchar_test.value, 0)) > + 20.0) + AS f2 + WHERE f1.key = f2.key + GROUP BY 1) AS foo; +SELECT * FROM insert_select_varchar_test ORDER BY 1 DESC, 2 DESC; + key | value +--------------------------------------------------------------------- + test_2 | 100 + test_2 | 30 + test_1 | 10 +(3 rows) + +-- some tests with DEFAULT columns and constant values +-- this test is mostly importantly intended for deparsing the query correctly +-- but still it is preferable to have this test here instead of multi_deparse_shard_query +CREATE TABLE table_with_defaults +( + store_id int, + first_name text, + default_1 int DEFAULT 1, + last_name text, + default_2 text DEFAULT '2' +); +-- we don't need many shards +SET citus.shard_count = 2; +SELECT create_distributed_table('table_with_defaults', 'store_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- let's see the queries +SET client_min_messages TO DEBUG2; +-- a very simple query +INSERT INTO table_with_defaults SELECT * FROM table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, default_1, last_name, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, default_1, last_name, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- see that defaults are filled +INSERT INTO table_with_defaults (store_id, first_name) +SELECT + store_id, first_name +FROM + table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- shuffle one of the defaults and skip the other +INSERT INTO table_with_defaults (default_2, store_id, first_name) +SELECT + default_2, store_id, first_name +FROM + table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, 1 AS default_1, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- shuffle both defaults +INSERT INTO table_with_defaults (default_2, store_id, default_1, first_name) +SELECT + default_2, store_id, default_1, first_name +FROM + table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, default_1, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, first_name, default_1, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- use constants instead of non-default column +INSERT INTO table_with_defaults (default_2, last_name, store_id, first_name) +SELECT + default_2, 'Freund', store_id, 'Andres' +FROM + table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- use constants instead of non-default column and skip both defauls +INSERT INTO table_with_defaults (last_name, store_id, first_name) +SELECT + 'Freund', store_id, 'Andres' +FROM + table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, 'Freund'::text AS last_name, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- use constants instead of default columns +INSERT INTO table_with_defaults (default_2, last_name, store_id, first_name, default_1) +SELECT + 20, last_name, store_id, first_name, 10 +FROM + table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, 10, last_name, 20 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, first_name, 10, last_name, 20 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- use constants instead of both default columns and non-default columns +INSERT INTO table_with_defaults (default_2, last_name, store_id, first_name, default_1) +SELECT + 20, 'Freund', store_id, 'Andres', 10 +FROM + table_with_defaults; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 10, 'Freund'::text AS last_name, 20 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, last_name, default_2) SELECT store_id, 'Andres'::text AS first_name, 10, 'Freund'::text AS last_name, 20 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) +-- some of the ultimate queries where we have constants, +-- defaults and group by entry is not on the target entry +INSERT INTO table_with_defaults (default_2, store_id, first_name) +SELECT + '2000', store_id, 'Andres' +FROM + table_with_defaults +GROUP BY + last_name, store_id; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1 AS default_1, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id +INSERT INTO table_with_defaults (default_1, store_id, first_name, default_2) +SELECT + 1000, store_id, 'Andres', '2000' +FROM + table_with_defaults +GROUP BY + last_name, store_id, first_name; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name +INSERT INTO table_with_defaults (default_1, store_id, first_name, default_2) +SELECT + 1000, store_id, 'Andres', '2000' +FROM + table_with_defaults +GROUP BY + last_name, store_id, first_name, default_2; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2000'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 +INSERT INTO table_with_defaults (default_1, store_id, first_name) +SELECT + 1000, store_id, 'Andres' +FROM + table_with_defaults +GROUP BY + last_name, store_id, first_name, default_2; +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300017 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2'::text AS default_2 FROM public.table_with_defaults_13300017 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 +DEBUG: distributed statement: INSERT INTO public.table_with_defaults_13300018 AS citus_table_alias (store_id, first_name, default_1, default_2) SELECT store_id, 'Andres'::text AS first_name, 1000, '2'::text AS default_2 FROM public.table_with_defaults_13300018 table_with_defaults WHERE (store_id IS NOT NULL) GROUP BY last_name, store_id, first_name, default_2 +RESET client_min_messages; +-- Stable function in default should be allowed +ALTER TABLE table_with_defaults ADD COLUMN t timestamptz DEFAULT now(); +INSERT INTO table_with_defaults (store_id, first_name, last_name) +SELECT + store_id, 'first '||store_id, 'last '||store_id +FROM + table_with_defaults +GROUP BY + store_id, first_name, last_name; +-- Volatile function in default should be disallowed - SERIAL pseudo-types +CREATE TABLE table_with_serial ( + store_id int, + s bigserial +); +SELECT create_distributed_table('table_with_serial', 'store_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO table_with_serial (store_id) +SELECT + store_id +FROM + table_with_defaults +GROUP BY + store_id; +-- Volatile function in default should be disallowed - user-defined sequence +CREATE SEQUENCE user_defined_sequence; +CREATE TABLE table_with_user_sequence ( + store_id int, + s bigint default nextval('user_defined_sequence') +); +SELECT create_distributed_table('table_with_user_sequence', 'store_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO table_with_user_sequence (store_id) +SELECT + store_id +FROM + table_with_defaults +GROUP BY + store_id; +-- do some more error/error message checks +SET citus.shard_count TO 4; +SET citus.shard_replication_factor TO 1; +CREATE TABLE text_table (part_col text, val int); +CREATE TABLE char_table (part_col char[], val int); +create table table_with_starts_with_defaults (a int DEFAULT 5, b int, c int); +SELECT create_distributed_table('text_table', 'part_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('char_table','part_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('table_with_starts_with_defaults', 'c'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG; +INSERT INTO text_table (part_col) + SELECT + CASE WHEN part_col = 'onder' THEN 'marco' + END +FROM text_table ; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains a case expression 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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT COALESCE(part_col, 'onder') FROM text_table; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains a coalesce expression 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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT GREATEST(part_col, 'jason') FROM text_table; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains a min/max expression 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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT LEAST(part_col, 'andres') FROM text_table; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains a min/max expression 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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT NULLIF(part_col, 'metin') FROM text_table; +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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT part_col isnull FROM text_table; +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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT part_col::text from char_table; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains an explicit coercion 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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT (part_col = 'burak') is true FROM text_table; +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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT val FROM text_table; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The data type of the target table's partition column should exactly match the data type of the corresponding simple column reference in the subquery. +DEBUG: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +INSERT INTO text_table (part_col) SELECT val::text FROM text_table; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: Subquery contains an explicit coercion 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: Router planner cannot handle multi-shard select queries +DEBUG: performing repartitioned INSERT ... SELECT +DEBUG: partitioning SELECT query by column index 0 with name 'part_col' +RESET client_min_messages; +insert into table_with_starts_with_defaults (b,c) select b,c FROM table_with_starts_with_defaults; +-- Test on partition column without native hash function +CREATE TABLE raw_table +( + id BIGINT, + time DATE +); +CREATE TABLE summary_table +( + time DATE, + count BIGINT +); +SELECT create_distributed_table('raw_table', 'time'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('summary_table', 'time'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO raw_table VALUES(1, '11-11-1980'); +INSERT INTO summary_table SELECT time, COUNT(*) FROM raw_table GROUP BY time; +SELECT * FROM summary_table; + time | count +--------------------------------------------------------------------- + 11-11-1980 | 1 +(1 row) + +-- Test INSERT ... SELECT via coordinator +-- Select from constants +TRUNCATE raw_events_first; +INSERT INTO raw_events_first (user_id, value_1) +SELECT * FROM (VALUES (1,2), (3,4), (5,6)) AS v(int,int); +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 2 + 3 | 4 + 5 | 6 +(3 rows) + +-- Select from local functions +TRUNCATE raw_events_first; +CREATE SEQUENCE insert_select_test_seq; +SET client_min_messages TO DEBUG; +INSERT INTO raw_events_first (user_id, value_1, value_2) +SELECT + s, nextval('insert_select_test_seq'), (random()*10)::int +FROM + generate_series(1, 5) s; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; +DEBUG: Router planner cannot handle multi-shard select queries + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +-- ON CONFLICT is supported +INSERT INTO raw_events_first (user_id, value_1) +SELECT s, nextval('insert_select_test_seq') FROM generate_series(1, 5) s +ON CONFLICT DO NOTHING; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300000'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300001'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300002'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300003'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) ON CONFLICT DO NOTHING +-- RETURNING is supported +INSERT INTO raw_events_first (user_id, value_1) +SELECT s, nextval('insert_select_test_seq') FROM generate_series(1, 5) s +RETURNING *; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300000 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300000'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300001 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300001'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300002 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300002'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 +DEBUG: distributed statement: INSERT INTO public.raw_events_first_13300003 AS citus_table_alias (user_id, value_1) SELECT user_id, value_1 FROM read_intermediate_result('insert_select_XXX_13300003'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, value_1 integer) RETURNING citus_table_alias.user_id, citus_table_alias."time", citus_table_alias.value_1, citus_table_alias.value_2, citus_table_alias.value_3, citus_table_alias.value_4 + user_id | time | value_1 | value_2 | value_3 | value_4 +--------------------------------------------------------------------- + 1 | | 11 | | | + 2 | | 12 | | | + 3 | | 13 | | | + 4 | | 14 | | | + 5 | | 15 | | | +(5 rows) + +RESET client_min_messages; +-- INSERT ... SELECT and multi-shard SELECT in the same transaction is supported +TRUNCATE raw_events_first; +BEGIN; +INSERT INTO raw_events_first (user_id, value_1) +SELECT s, s FROM generate_series(1, 5) s; +SELECT user_id, value_1 FROM raw_events_first ORDER BY 1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +ROLLBACK; +-- INSERT ... SELECT and single-shard SELECT in the same transaction is supported +TRUNCATE raw_events_first; +BEGIN; +INSERT INTO raw_events_first (user_id, value_1) +SELECT s, s FROM generate_series(1, 5) s; +SELECT user_id, value_1 FROM raw_events_first WHERE user_id = 1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +COMMIT; +-- Select from local table +TRUNCATE raw_events_first; +CREATE TEMPORARY TABLE raw_events_first_local AS +SELECT s AS u, 2*s AS v FROM generate_series(1, 5) s; +INSERT INTO raw_events_first (user_id, value_1) +SELECT u, v FROM raw_events_first_local; +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 2 + 2 | 4 + 3 | 6 + 4 | 8 + 5 | 10 +(5 rows) + +-- Use columns in opposite order +TRUNCATE raw_events_first; +INSERT INTO raw_events_first (value_1, user_id) +SELECT u, v FROM raw_events_first_local; +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 2 | 1 + 4 | 2 + 6 | 3 + 8 | 4 + 10 | 5 +(5 rows) + +-- Set operations can work with opposite column order +TRUNCATE raw_events_first; +INSERT INTO raw_events_first (value_3, user_id) +( SELECT v, u::bigint FROM raw_events_first_local ) +UNION ALL +( SELECT v, u FROM raw_events_first_local ); +SELECT user_id, value_3 FROM raw_events_first ORDER BY user_id, value_3; + user_id | value_3 +--------------------------------------------------------------------- + 1 | 2 + 1 | 2 + 2 | 4 + 2 | 4 + 3 | 6 + 3 | 6 + 4 | 8 + 4 | 8 + 5 | 10 + 5 | 10 +(10 rows) + +-- Select from other distributed table with limit +TRUNCATE raw_events_first; +TRUNCATE raw_events_second; +INSERT INTO raw_events_second (user_id, value_4) +SELECT s, 3*s FROM generate_series (1,5) s; +INSERT INTO raw_events_first (user_id, value_1) +SELECT user_id, value_4 FROM raw_events_second LIMIT 5; +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 + 5 | 15 +(5 rows) + +-- CTEs are supported in local queries +TRUNCATE raw_events_first; +WITH removed_rows AS ( + DELETE FROM raw_events_first_local RETURNING u +) +INSERT INTO raw_events_first (user_id, value_1) +WITH value AS (SELECT 1) +SELECT * FROM removed_rows, value; +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 1 + 3 | 1 + 4 | 1 + 5 | 1 +(5 rows) + +-- nested CTEs are also supported +TRUNCATE raw_events_first; +INSERT INTO raw_events_first_local SELECT s, 2*s FROM generate_series(0, 10) s; +WITH rows_to_remove AS ( + SELECT u FROM raw_events_first_local WHERE u > 0 +), +removed_rows AS ( + DELETE FROM raw_events_first_local + WHERE u IN (SELECT * FROM rows_to_remove) + RETURNING u, v +) +INSERT INTO raw_events_first (user_id, value_1) +WITH ultra_rows AS ( + WITH numbers AS ( + SELECT s FROM generate_series(1,10) s + ), + super_rows AS ( + SELECT u, v FROM removed_rows JOIN numbers ON (u = s) + ) + SELECT * FROM super_rows LIMIT 5 +) +SELECT u, v FROM ultra_rows; +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 2 + 2 | 4 + 3 | 6 + 4 | 8 + 5 | 10 +(5 rows) + +-- CTEs with duplicate names are also supported +TRUNCATE raw_events_first; +WITH super_rows AS ( + SELECT u FROM raw_events_first_local +) +INSERT INTO raw_events_first (user_id, value_1) +WITH super_rows AS ( + SELECT * FROM super_rows GROUP BY u +) +SELECT u, 5 FROM super_rows; +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 0 | 5 +(1 row) + +-- CTEs are supported in router queries +TRUNCATE raw_events_first; +WITH user_two AS ( + SELECT user_id, value_4 FROM raw_events_second WHERE user_id = 2 +) +INSERT INTO raw_events_first (user_id, value_1) +SELECT * FROM user_two; +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 2 | 6 +(1 row) + +-- CTEs are supported when there are name collisions +WITH numbers AS ( + SELECT s FROM generate_series(1,10) s +) +INSERT INTO raw_events_first(user_id, value_1) +WITH numbers AS ( + SELECT s, s FROM generate_series(1,5) s +) +SELECT * FROM numbers; +-- Select into distributed table with a sequence +CREATE TABLE "CaseSensitiveTable" ("UserID" int, "Value1" int); +SELECT create_distributed_table('"CaseSensitiveTable"', 'UserID'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO "CaseSensitiveTable" +SELECT s, s FROM generate_series(1,10) s; +SELECT * FROM "CaseSensitiveTable" ORDER BY "UserID"; + UserID | Value1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 + 10 | 10 +(10 rows) + +DROP TABLE "CaseSensitiveTable"; +-- Select into distributed table with a sequence +CREATE TABLE dist_table_with_sequence (user_id serial, value_1 serial); +SELECT create_distributed_table('dist_table_with_sequence', 'user_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- from local query +INSERT INTO dist_table_with_sequence (value_1) +SELECT s FROM generate_series(1,5) s; +SELECT * FROM dist_table_with_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +-- from a distributed query +INSERT INTO dist_table_with_sequence (value_1) +SELECT value_1 FROM dist_table_with_sequence ORDER BY value_1; +SELECT * FROM dist_table_with_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 1 + 7 | 2 + 8 | 3 + 9 | 4 + 10 | 5 +(10 rows) + +TRUNCATE dist_table_with_sequence; +INSERT INTO dist_table_with_sequence (user_id) +SELECT user_id FROM raw_events_second ORDER BY user_id; +SELECT * FROM dist_table_with_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +WITH top10 AS ( + SELECT user_id FROM raw_events_second WHERE value_1 IS NOT NULL ORDER BY value_1 LIMIT 10 +) +INSERT INTO dist_table_with_sequence (value_1) +SELECT * FROM top10; +SELECT * FROM dist_table_with_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +-- router queries become logical planner queries when there is a nextval call +INSERT INTO dist_table_with_sequence (user_id) +SELECT user_id FROM dist_table_with_sequence WHERE user_id = 1; +SELECT * FROM dist_table_with_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 1 | 6 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(6 rows) + +DROP TABLE dist_table_with_sequence; +-- Select into distributed table with a user-defined sequence +CREATE SEQUENCE seq1; +CREATE SEQUENCE seq2; +CREATE TABLE dist_table_with_user_sequence (user_id int default nextval('seq1'), value_1 bigint default nextval('seq2')); +SELECT create_distributed_table('dist_table_with_user_sequence', 'user_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- from local query +INSERT INTO dist_table_with_user_sequence (value_1) +SELECT s FROM generate_series(1,5) s; +SELECT * FROM dist_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +-- from a distributed query +INSERT INTO dist_table_with_user_sequence (value_1) +SELECT value_1 FROM dist_table_with_user_sequence ORDER BY value_1; +SELECT * FROM dist_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 1 + 7 | 2 + 8 | 3 + 9 | 4 + 10 | 5 +(10 rows) + +TRUNCATE dist_table_with_user_sequence; +INSERT INTO dist_table_with_user_sequence (user_id) +SELECT user_id FROM raw_events_second ORDER BY user_id; +SELECT * FROM dist_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +WITH top10 AS ( + SELECT user_id FROM raw_events_second WHERE value_1 IS NOT NULL ORDER BY value_1 LIMIT 10 +) +INSERT INTO dist_table_with_user_sequence (value_1) +SELECT * FROM top10; +SELECT * FROM dist_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +-- router queries become logical planner queries when there is a nextval call +INSERT INTO dist_table_with_user_sequence (user_id) +SELECT user_id FROM dist_table_with_user_sequence WHERE user_id = 1; +SELECT * FROM dist_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 1 + 1 | 6 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(6 rows) + +DROP TABLE dist_table_with_user_sequence; +DROP SEQUENCE seq1, seq2; +-- Select from distributed table into reference table +CREATE TABLE ref_table (user_id serial, value_1 int); +SELECT create_reference_table('ref_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO ref_table +SELECT user_id, value_1 FROM raw_events_second; +SELECT * FROM ref_table ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | + 2 | + 3 | + 4 | + 5 | +(5 rows) + +INSERT INTO ref_table (value_1) +SELECT value_1 FROM raw_events_second ORDER BY value_1; +SELECT * FROM ref_table ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | + 1 | + 2 | + 2 | + 3 | + 3 | + 4 | + 4 | + 5 | + 5 | +(10 rows) + +INSERT INTO ref_table SELECT * FROM ref_table; +SELECT * FROM ref_table ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | + 1 | + 1 | + 1 | + 2 | + 2 | + 2 | + 2 | + 3 | + 3 | + 3 | + 3 | + 4 | + 4 | + 4 | + 4 | + 5 | + 5 | + 5 | + 5 | +(20 rows) + +DROP TABLE ref_table; +-- Select from distributed table into reference table with user-defined sequence +CREATE SEQUENCE seq1; +CREATE TABLE ref_table_with_user_sequence (user_id int default nextval('seq1'), value_1 int); +SELECT create_reference_table('ref_table_with_user_sequence'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO ref_table_with_user_sequence +SELECT user_id, value_1 FROM raw_events_second; +SELECT * FROM ref_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | + 2 | + 3 | + 4 | + 5 | +(5 rows) + +INSERT INTO ref_table_with_user_sequence (value_1) +SELECT value_1 FROM raw_events_second ORDER BY value_1; +SELECT * FROM ref_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | + 1 | + 2 | + 2 | + 3 | + 3 | + 4 | + 4 | + 5 | + 5 | +(10 rows) + +INSERT INTO ref_table_with_user_sequence SELECT * FROM ref_table_with_user_sequence; +SELECT * FROM ref_table_with_user_sequence ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | + 1 | + 1 | + 1 | + 2 | + 2 | + 2 | + 2 | + 3 | + 3 | + 3 | + 3 | + 4 | + 4 | + 4 | + 4 | + 5 | + 5 | + 5 | + 5 | +(20 rows) + +DROP TABLE ref_table_with_user_sequence; +DROP SEQUENCE seq1; +-- Select from reference table into reference table +CREATE TABLE ref1 (d timestamptz); +SELECT create_reference_table('ref1'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE ref2 (d date); +SELECT create_reference_table('ref2'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO ref2 VALUES ('2017-10-31'); +INSERT INTO ref1 SELECT * FROM ref2; +SELECT count(*) from ref1; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- also test with now() +INSERT INTO ref1 SELECT now() FROM ref2; +SELECT count(*) from ref1; + count +--------------------------------------------------------------------- + 2 +(1 row) + +DROP TABLE ref1; +DROP TABLE ref2; +-- Select into an append-partitioned table is not supported +CREATE TABLE insert_append_table (user_id int, value_4 bigint); +SELECT create_distributed_table('insert_append_table', 'user_id', 'append'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO insert_append_table (user_id, value_4) +SELECT user_id, 1 FROM raw_events_second LIMIT 5; +ERROR: INSERT ... SELECT into an append-distributed table is not supported +DROP TABLE insert_append_table; +-- Insert from other distributed table as prepared statement +TRUNCATE raw_events_first; +PREPARE insert_prep(int) AS +INSERT INTO raw_events_first (user_id, value_1) +SELECT $1, value_4 FROM raw_events_second ORDER BY value_4 LIMIT 1; +EXECUTE insert_prep(1); +EXECUTE insert_prep(2); +EXECUTE insert_prep(3); +EXECUTE insert_prep(4); +EXECUTE insert_prep(5); +EXECUTE insert_prep(6); +SELECT user_id, value_1 FROM raw_events_first ORDER BY user_id, value_1; + user_id | value_1 +--------------------------------------------------------------------- + 1 | 3 + 2 | 3 + 3 | 3 + 4 | 3 + 5 | 3 + 6 | 3 +(6 rows) + +-- Inserting into views is handled via coordinator +TRUNCATE raw_events_first; +INSERT INTO test_view +SELECT * FROM raw_events_second; +SELECT user_id, value_4 FROM test_view ORDER BY user_id, value_4; + user_id | value_4 +--------------------------------------------------------------------- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 + 5 | 15 +(5 rows) + +-- Drop the view now, because the column we are about to drop depends on it +DROP VIEW test_view; +-- Make sure we handle dropped columns correctly +CREATE TABLE drop_col_table (col1 text, col2 text, col3 text); +SELECT create_distributed_table('drop_col_table', 'col2'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE drop_col_table DROP COLUMN col1; +INSERT INTO drop_col_table (col3, col2) +SELECT value_4, user_id FROM raw_events_second LIMIT 5; +SELECT * FROM drop_col_table ORDER BY col2, col3; + col2 | col3 +--------------------------------------------------------------------- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 + 5 | 15 +(5 rows) + +-- make sure the tuple went to the right shard +SELECT * FROM drop_col_table WHERE col2 = '1'; + col2 | col3 +--------------------------------------------------------------------- + 1 | 3 +(1 row) + +RESET client_min_messages; +-- make sure casts are handled correctly +CREATE TABLE coerce_events(user_id int, time timestamp, value_1 numeric); +SELECT create_distributed_table('coerce_events', 'user_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE coerce_agg (user_id int, value_1_agg int); +SELECT create_distributed_table('coerce_agg', 'user_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO coerce_events(user_id, value_1) VALUES (1, 1), (2, 2), (10, 10); +-- numeric -> int (straight function) +INSERT INTO coerce_agg(user_id, value_1_agg) +SELECT * +FROM ( + SELECT user_id, value_1 + FROM coerce_events +) AS ftop +ORDER BY 2 DESC, 1 DESC +LIMIT 5; +-- int -> text +ALTER TABLE coerce_agg ALTER COLUMN value_1_agg TYPE text; +INSERT INTO coerce_agg(user_id, value_1_agg) +SELECT * +FROM ( + SELECT user_id, value_1 + FROM coerce_events +) AS ftop +LIMIT 5; +SELECT * FROM coerce_agg ORDER BY 1 DESC, 2 DESC; + user_id | value_1_agg +--------------------------------------------------------------------- + 10 | 10 + 10 | 10 + 2 | 2 + 2 | 2 + 1 | 1 + 1 | 1 +(6 rows) + +TRUNCATE coerce_agg; +-- int -> char(1) +ALTER TABLE coerce_agg ALTER COLUMN value_1_agg TYPE char(1); +INSERT INTO coerce_agg(user_id, value_1_agg) +SELECT * +FROM ( + SELECT user_id, value_1 + FROM coerce_events +) AS ftop +LIMIT 5; +ERROR: value too long for type character(1) +SELECT * FROM coerce_agg ORDER BY 1 DESC, 2 DESC; + user_id | value_1_agg +--------------------------------------------------------------------- +(0 rows) + +TRUNCATE coerce_agg; +TRUNCATE coerce_events; +-- char(5) -> char(1) +ALTER TABLE coerce_events ALTER COLUMN value_1 TYPE char(5); +INSERT INTO coerce_events(user_id, value_1) VALUES (1, 'aaaaa'), (2, 'bbbbb'); +INSERT INTO coerce_agg(user_id, value_1_agg) +SELECT * +FROM ( + SELECT user_id, value_1 + FROM coerce_events +) AS ftop +LIMIT 5; +ERROR: value too long for type character(1) +-- char(1) -> char(5) +ALTER TABLE coerce_events ALTER COLUMN value_1 TYPE char(1) USING value_1::char(1); +ALTER TABLE coerce_agg ALTER COLUMN value_1_agg TYPE char(5); +INSERT INTO coerce_agg(user_id, value_1_agg) +SELECT * +FROM ( + SELECT user_id, value_1 + FROM coerce_events +) AS ftop +LIMIT 5; +SELECT * FROM coerce_agg ORDER BY 1 DESC, 2 DESC; + user_id | value_1_agg +--------------------------------------------------------------------- + 2 | b + 1 | a +(2 rows) + +TRUNCATE coerce_agg; +TRUNCATE coerce_events; +-- integer -> integer (check VALUE < 5) +ALTER TABLE coerce_events ALTER COLUMN value_1 TYPE integer USING NULL; +ALTER TABLE coerce_agg ALTER COLUMN value_1_agg TYPE integer USING NULL; +ALTER TABLE coerce_agg ADD CONSTRAINT small_number CHECK (value_1_agg < 5); +INSERT INTO coerce_events (user_id, value_1) VALUES (1, 1), (10, 10); +\set VERBOSITY TERSE +INSERT INTO coerce_agg(user_id, value_1_agg) +SELECT * +FROM ( + SELECT user_id, value_1 + FROM coerce_events +) AS ftop; +ERROR: new row for relation "coerce_agg_13300067" violates check constraint "small_number_13300067" +\set VERBOSITY DEFAULT +SELECT * FROM coerce_agg ORDER BY 1 DESC, 2 DESC; + user_id | value_1_agg +--------------------------------------------------------------------- +(0 rows) + +-- integer[3] -> text[3] +TRUNCATE coerce_events; +ALTER TABLE coerce_events ALTER COLUMN value_1 TYPE integer[3] USING NULL; +INSERT INTO coerce_events(user_id, value_1) VALUES (1, '{1,1,1}'), (2, '{2,2,2}'); +ALTER TABLE coerce_agg DROP COLUMN value_1_agg; +ALTER TABLE coerce_agg ADD COLUMN value_1_agg text[3]; +INSERT INTO coerce_agg(user_id, value_1_agg) +SELECT * +FROM ( + SELECT user_id, value_1 + FROM coerce_events +) AS ftop +LIMIT 5; +SELECT * FROM coerce_agg ORDER BY 1 DESC, 2 DESC; + user_id | value_1_agg +--------------------------------------------------------------------- + 2 | {2,2,2} + 1 | {1,1,1} +(2 rows) + +-- INSERT..SELECT + prepared statements + recursive planning +BEGIN; +PREPARE prepared_recursive_insert_select AS +INSERT INTO users_table +SELECT * FROM users_table +WHERE value_1 IN (SELECT value_2 FROM events_table OFFSET 0); +EXECUTE prepared_recursive_insert_select; +EXECUTE prepared_recursive_insert_select; +EXECUTE prepared_recursive_insert_select; +EXECUTE prepared_recursive_insert_select; +EXECUTE prepared_recursive_insert_select; +EXECUTE prepared_recursive_insert_select; +ROLLBACK; +-- upsert with on conflict update distribution column is unsupported +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 user_id = 42 +RETURNING user_id, value_1_agg; +ERROR: modifying the partition value of rows is not allowed +-- test a small citus.remote_copy_flush_threshold +BEGIN; +SET LOCAL citus.remote_copy_flush_threshold TO 1; +INSERT INTO raw_events_first +SELECT * FROM raw_events_first OFFSET 0 +ON CONFLICT DO NOTHING; +ABORT; +-- test fix for issue https://github.com/citusdata/citus/issues/5891 +CREATE TABLE dist_table_1( +dist_col integer, +int_col integer, +text_col_1 text, +text_col_2 text +); +SELECT create_distributed_table('dist_table_1', 'dist_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO dist_table_1 VALUES (1, 1, 'string', 'string'); +CREATE TABLE dist_table_2( +dist_col integer, +int_col integer +); +SELECT create_distributed_table('dist_table_2', 'dist_col'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO dist_table_2 VALUES (1, 1); +with a as (select random()) INSERT INTO dist_table_1 +SELECT +t1.dist_col, +1, +'string', +'string' +FROM a, dist_table_1 t1 +join dist_table_2 t2 using (dist_col) +limit 1 +returning text_col_1; + text_col_1 +--------------------------------------------------------------------- + string +(1 row) + +DROP TABLE dist_table_1, dist_table_2; +-- wrap in a transaction to improve performance +BEGIN; +DROP TABLE coerce_events; +DROP TABLE coerce_agg; +DROP TABLE drop_col_table; +DROP TABLE raw_table; +DROP TABLE summary_table; +DROP TABLE raw_events_first CASCADE; +DROP TABLE raw_events_second; +DROP TABLE reference_table; +DROP TABLE agg_events; +DROP TABLE table_with_defaults; +DROP TABLE table_with_serial; +DROP TABLE table_with_user_sequence; +DROP SEQUENCE user_defined_sequence; +DROP TABLE text_table; +DROP TABLE char_table; +DROP TABLE table_with_starts_with_defaults; +COMMIT; diff --git a/src/test/regress/expected/multi_insert_select_conflict.out b/src/test/regress/expected/multi_insert_select_conflict.out index 5f2344432..df7bdc9b9 100644 --- a/src/test/regress/expected/multi_insert_select_conflict.out +++ b/src/test/regress/expected/multi_insert_select_conflict.out @@ -1,3 +1,17 @@ +-- +-- MULTI_INSERT_SELECT_CONFLICT +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA on_conflict; SET search_path TO on_conflict, public; SET citus.next_shard_id TO 1900000; @@ -66,7 +80,7 @@ WITH inserted_table AS ( source_table_1 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_2, col_3 FROM on_conflict.source_table_1 ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 RETURNING target_table.col_1, target_table.col_2 +DEBUG: generating subplan XXX_1 for CTE inserted_table: INSERT INTO on_conflict.target_table (col_1, col_2) SELECT source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1 ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 RETURNING target_table.col_1, target_table.col_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: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, 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)) inserted_table ORDER BY col_1 @@ -112,7 +126,7 @@ WITH inserted_table AS ( ) as foo 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: 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: 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 @@ -148,7 +162,7 @@ WITH inserted_table AS ( ) as foo 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: 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: 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 @@ -239,7 +253,7 @@ WITH inserted_table AS MATERIALIZED ( ) INSERT INTO target_table SELECT * FROM cte_2 ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1 RETURNING * ) SELECT * FROM inserted_table ORDER BY 1; -DEBUG: generating subplan XXX_1 for CTE inserted_table: WITH cte AS MATERIALIZED (SELECT source_table_1.col_1, source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1), cte_2 AS MATERIALIZED (SELECT cte.col_1, cte.col_2 FROM cte) INSERT INTO on_conflict.target_table (col_1, col_2) SELECT col_1, col_2 FROM cte_2 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: generating subplan XXX_1 for CTE inserted_table: WITH cte AS MATERIALIZED (SELECT source_table_1.col_1, source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1), cte_2 AS MATERIALIZED (SELECT cte.col_1, cte.col_2 FROM cte) INSERT INTO on_conflict.target_table (col_1, col_2) SELECT cte_2.col_1, cte_2.col_2 FROM cte_2 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: distributed INSERT ... SELECT can only select from distributed tables DEBUG: generating subplan XXX_1 for CTE cte: SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 DEBUG: generating subplan XXX_2 for CTE cte_2: 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)) cte @@ -262,7 +276,7 @@ WITH cte AS MATERIALIZED ( INSERT INTO target_table (SELECT * FROM basic) ON CONFLICT DO NOTHING RETURNING * ) UPDATE target_table SET col_2 = 4 WHERE col_1 IN (SELECT col_1 FROM cte); -DEBUG: generating subplan XXX_1 for CTE cte: WITH basic AS MATERIALIZED (SELECT source_table_1.col_1, source_table_1.col_2 FROM on_conflict.source_table_1) INSERT INTO on_conflict.target_table (col_1, col_2) SELECT col_1, col_2 FROM basic ON CONFLICT DO NOTHING RETURNING target_table.col_1, target_table.col_2 +DEBUG: generating subplan XXX_1 for CTE cte: WITH basic AS MATERIALIZED (SELECT source_table_1.col_1, source_table_1.col_2 FROM on_conflict.source_table_1) INSERT INTO on_conflict.target_table (col_1, col_2) SELECT basic.col_1, basic.col_2 FROM basic ON CONFLICT DO NOTHING RETURNING target_table.col_1, target_table.col_2 DEBUG: distributed INSERT ... SELECT can only select from distributed tables DEBUG: generating subplan XXX_1 for CTE basic: SELECT col_1, col_2 FROM on_conflict.source_table_1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT basic.col_1, basic.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)) basic) citus_insert_select_subquery @@ -557,7 +571,7 @@ 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: 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: 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 diff --git a/src/test/regress/expected/multi_insert_select_conflict_0.out b/src/test/regress/expected/multi_insert_select_conflict_0.out new file mode 100644 index 000000000..b8f926d30 --- /dev/null +++ b/src/test/regress/expected/multi_insert_select_conflict_0.out @@ -0,0 +1,593 @@ +-- +-- MULTI_INSERT_SELECT_CONFLICT +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA on_conflict; +SET search_path TO on_conflict, public; +SET citus.next_shard_id TO 1900000; +SET citus.shard_replication_factor TO 1; +CREATE TABLE target_table(col_1 int primary key, col_2 int); +SELECT create_distributed_table('target_table','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO target_table VALUES(1,2),(2,3),(3,4),(4,5),(5,6); +CREATE TABLE source_table_1(col_1 int primary key, col_2 int, col_3 int); +SELECT create_distributed_table('source_table_1','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table_1 VALUES(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5); +CREATE TABLE source_table_2(col_1 int, col_2 int, col_3 int); +SELECT create_distributed_table('source_table_2','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table_2 VALUES(6,6,6),(7,7,7),(8,8,8),(9,9,9),(10,10,10); +SET client_min_messages to debug1; +-- Generate series directly on the coordinator and on conflict do nothing +INSERT INTO target_table (col_1, col_2) +SELECT + s, s +FROM + generate_series(1,10) s +ON CONFLICT DO NOTHING; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Generate series directly on the coordinator and on conflict update the target table +INSERT INTO target_table (col_1, col_2) +SELECT s, s +FROM + generate_series(1,10) s +ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Since partition columns do not match, pull the data to the coordinator +-- and do not change conflicted values +INSERT INTO target_table +SELECT + col_2, col_3 +FROM + source_table_1 +ON CONFLICT DO NOTHING; +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: performing repartitioned INSERT ... SELECT +-- Since partition columns do not match, pull the data to the coordinator +-- and update the non-partition column. Query is wrapped by CTE to return +-- ordered result. +WITH inserted_table AS ( + INSERT INTO target_table + SELECT + col_2, col_3 + FROM + source_table_1 + 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_2, col_3 FROM on_conflict.source_table_1 ON CONFLICT(col_1) DO UPDATE SET col_2 = excluded.col_2 RETURNING target_table.col_1, target_table.col_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: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, 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)) inserted_table ORDER BY col_1 +DEBUG: performing repartitioned INSERT ... SELECT + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +-- Subquery should be recursively planned due to the limit and do nothing on conflict +INSERT INTO target_table +SELECT + col_1, col_2 +FROM ( + SELECT + col_1, col_2, col_3 + FROM + source_table_1 + LIMIT 5 +) as foo +ON CONFLICT DO NOTHING; +DEBUG: LIMIT clauses are not allowed in distributed INSERT ... SELECT queries +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 +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Subquery should be recursively planned due to the limit and update on conflict +-- Query is wrapped by CTE to return ordered result. +WITH inserted_table AS ( + INSERT INTO target_table + SELECT + col_1, col_2 + FROM ( + SELECT + col_1, col_2, col_3 + FROM + source_table_1 + LIMIT 5 + ) as foo + 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: 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 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, 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)) inserted_table ORDER BY col_1 +DEBUG: Collecting INSERT ... SELECT results on coordinator + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 +(5 rows) + +-- Test with multiple subqueries. Query is wrapped by CTE to return ordered result. +WITH inserted_table AS ( + INSERT INTO target_table + SELECT + col_1, col_2 + FROM ( + (SELECT + col_1, col_2, col_3 + FROM + source_table_1 + LIMIT 5) + UNION + (SELECT + col_1, col_2, col_3 + FROM + source_table_2 + LIMIT 5) + ) as foo + 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: 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 +DEBUG: generating subplan XXX_2 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_2 LIMIT 5 +DEBUG: generating subplan XXX_3 for subquery 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) UNION SELECT intermediate_result.col_1, intermediate_result.col_2, intermediate_result.col_3 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer) +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_3'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer)) foo +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, 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)) inserted_table ORDER BY col_1 +DEBUG: Collecting INSERT ... SELECT results on coordinator + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 0 + 2 | 0 + 3 | 0 + 4 | 0 + 5 | 0 + 6 | 0 + 7 | 0 + 8 | 0 + 9 | 0 + 10 | 0 +(10 rows) + +-- Get the select part from cte and do nothing on conflict +WITH cte AS MATERIALIZED ( + SELECT col_1, col_2 FROM source_table_1 +) +INSERT INTO target_table SELECT * FROM cte ON CONFLICT DO NOTHING; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: SELECT col_1, col_2 FROM on_conflict.source_table_1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT cte.col_1, cte.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) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Get the select part from cte and update on conflict +WITH cte AS MATERIALIZED ( + SELECT col_1, col_2 FROM source_table_1 +) +INSERT INTO target_table SELECT * FROM cte ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: SELECT col_1, col_2 FROM on_conflict.source_table_1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT cte.col_1, cte.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) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT * FROM target_table ORDER BY 1; + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 2 + 2 | 3 + 3 | 4 + 4 | 5 + 5 | 6 + 6 | 0 + 7 | 0 + 8 | 0 + 9 | 0 + 10 | 0 +(10 rows) + +-- Test with multiple CTEs +WITH cte AS( + SELECT col_1, col_2 FROM source_table_1 +), cte_2 AS( + SELECT col_1, col_2 FROM source_table_2 +) +INSERT INTO target_table ((SELECT * FROM cte) UNION (SELECT * FROM cte_2)) ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: CTE cte is going to be inlined via distributed planning +DEBUG: CTE cte_2 is going to be inlined via distributed planning +DEBUG: performing repartitioned INSERT ... SELECT +SELECT * FROM target_table ORDER BY 1; + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 2 + 2 | 3 + 3 | 4 + 4 | 5 + 5 | 6 + 6 | 7 + 7 | 8 + 8 | 9 + 9 | 10 + 10 | 11 +(10 rows) + +WITH inserted_table AS MATERIALIZED ( + WITH cte AS MATERIALIZED ( + SELECT col_1, col_2, col_3 FROM source_table_1 + ), cte_2 AS MATERIALIZED ( + SELECT col_1, col_2 FROM cte + ) + INSERT INTO target_table SELECT * FROM cte_2 ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1 RETURNING * +) SELECT * FROM inserted_table ORDER BY 1; +DEBUG: generating subplan XXX_1 for CTE inserted_table: WITH cte AS MATERIALIZED (SELECT source_table_1.col_1, source_table_1.col_2, source_table_1.col_3 FROM on_conflict.source_table_1), cte_2 AS MATERIALIZED (SELECT cte.col_1, cte.col_2 FROM cte) INSERT INTO on_conflict.target_table (col_1, col_2) SELECT col_1, col_2 FROM cte_2 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: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 +DEBUG: generating subplan XXX_2 for CTE cte_2: 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)) cte +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT cte_2.col_1, cte_2.col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer)) cte_2) citus_insert_select_subquery +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, 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)) inserted_table ORDER BY col_1 +DEBUG: Collecting INSERT ... SELECT results on coordinator + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 2 + 2 | 3 + 3 | 4 + 4 | 5 + 5 | 6 +(5 rows) + +WITH cte AS MATERIALIZED ( + WITH basic AS MATERIALIZED ( + SELECT col_1, col_2 FROM source_table_1 + ) + INSERT INTO target_table (SELECT * FROM basic) ON CONFLICT DO NOTHING RETURNING * +) +UPDATE target_table SET col_2 = 4 WHERE col_1 IN (SELECT col_1 FROM cte); +DEBUG: generating subplan XXX_1 for CTE cte: WITH basic AS MATERIALIZED (SELECT source_table_1.col_1, source_table_1.col_2 FROM on_conflict.source_table_1) INSERT INTO on_conflict.target_table (col_1, col_2) SELECT col_1, col_2 FROM basic ON CONFLICT DO NOTHING RETURNING target_table.col_1, target_table.col_2 +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: generating subplan XXX_1 for CTE basic: SELECT col_1, col_2 FROM on_conflict.source_table_1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT basic.col_1, basic.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)) basic) citus_insert_select_subquery +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE on_conflict.target_table SET col_2 = 4 WHERE (col_1 OPERATOR(pg_catalog.=) ANY (SELECT cte.col_1 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)) +DEBUG: Collecting INSERT ... SELECT results on coordinator +RESET client_min_messages; +-- Following query is supported by using repartition join for the insert/select +SELECT coordinator_plan($Q$ +EXPLAIN (costs off) +WITH cte AS ( + SELECT + col_1, col_2 + FROM + source_table_1 +) +INSERT INTO target_table +SELECT + source_table_1.col_1, + source_table_1.col_2 +FROM cte, source_table_1 +WHERE cte.col_1 = source_table_1.col_1 ON CONFLICT DO NOTHING; +$Q$); + coordinator_plan +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 +(4 rows) + +-- Tests with foreign key to reference table +CREATE TABLE test_ref_table (key int PRIMARY KEY); +SELECT create_reference_table('test_ref_table'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO test_ref_table VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10); +ALTER TABLE target_table ADD CONSTRAINT fkey FOREIGN KEY (col_1) REFERENCES test_ref_table(key) ON DELETE CASCADE; +BEGIN; + TRUNCATE test_ref_table CASCADE; +NOTICE: truncate cascades to table "target_table" + INSERT INTO + target_table + SELECT + col_2, + col_1 + FROM source_table_1 ON CONFLICT (col_1) DO UPDATE SET col_2 = 55 RETURNING *; +ERROR: insert or update on table "target_table_xxxxxxx" violates foreign key constraint "fkey_xxxxxxx" +DETAIL: Key (col_1)=(X) is not present in table "test_ref_table_xxxxxxx". +CONTEXT: while executing command on localhost:xxxxx +ROLLBACK; +BEGIN; + DELETE FROM test_ref_table WHERE key > 10; + WITH r AS ( + INSERT INTO + target_table + SELECT + col_2, + col_1 + FROM source_table_1 ON CONFLICT (col_1) DO UPDATE SET col_2 = 1 RETURNING *) + SELECT * FROM r ORDER BY col_1; + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 1 + 2 | 1 + 3 | 1 + 4 | 1 + 5 | 1 +(5 rows) + +ROLLBACK; +-- Following two queries are supported since we no not modify but only select from +-- the target_table after modification on test_ref_table. +BEGIN; + TRUNCATE test_ref_table CASCADE; +NOTICE: truncate cascades to table "target_table" + INSERT INTO + source_table_1 + SELECT + col_2, + col_1 + FROM target_table ON CONFLICT (col_1) DO UPDATE SET col_2 = 55 RETURNING *; + col_1 | col_2 | col_3 +--------------------------------------------------------------------- +(0 rows) + +ROLLBACK; +BEGIN; + DELETE FROM test_ref_table; + INSERT INTO + source_table_1 + SELECT + col_2, + col_1 + FROM target_table ON CONFLICT (col_1) DO UPDATE SET col_2 = 55 RETURNING *; + col_1 | col_2 | col_3 +--------------------------------------------------------------------- +(0 rows) + +ROLLBACK; +-- INSERT .. SELECT with different column types +CREATE TABLE source_table_3(col_1 numeric, col_2 numeric, col_3 numeric); +SELECT create_distributed_table('source_table_3','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table_3 VALUES(1,11,1),(2,22,2),(3,33,3),(4,44,4),(5,55,5); +CREATE TABLE source_table_4(id int, arr_val text[]); +SELECT create_distributed_table('source_table_4','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table_4 VALUES(1, '{"abc","cde","efg"}'), (2, '{"xyz","tvu"}'); +CREATE TABLE target_table_2(id int primary key, arr_val char(10)[]); +SELECT create_distributed_table('target_table_2','id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO target_table_2 VALUES(1, '{"abc","def","gyx"}'); +SET client_min_messages to debug1; +INSERT INTO target_table +SELECT + col_1, col_2 +FROM + source_table_3 +ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2; +DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match +DETAIL: The data type of the target table's partition column should exactly match the data type of the corresponding simple column reference in the subquery. +DEBUG: performing repartitioned INSERT ... SELECT +SELECT * FROM target_table ORDER BY 1; + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 11 + 2 | 22 + 3 | 33 + 4 | 44 + 5 | 55 + 6 | 7 + 7 | 8 + 8 | 9 + 9 | 10 + 10 | 11 +(10 rows) + +INSERT INTO target_table_2 +SELECT + * +FROM + source_table_4 +ON CONFLICT DO NOTHING; +SELECT * FROM target_table_2 ORDER BY 1; + id | arr_val +--------------------------------------------------------------------- + 1 | {"abc ","def ","gyx "} + 2 | {"xyz ","tvu "} +(2 rows) + +RESET client_min_messages; +-- Test with shard_replication_factor = 2 +SET citus.shard_replication_factor to 2; +DROP TABLE target_table, source_table_1, source_table_2; +CREATE TABLE target_table(col_1 int primary key, col_2 int); +SELECT create_distributed_table('target_table','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO target_table VALUES(1,2),(2,3),(3,4),(4,5),(5,6); +CREATE TABLE source_table_1(col_1 int, col_2 int, col_3 int); +SELECT create_distributed_table('source_table_1','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table_1 VALUES(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5); +CREATE TABLE source_table_2(col_1 int, col_2 int, col_3 int); +SELECT create_distributed_table('source_table_2','col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table_2 VALUES(6,6,6),(7,7,7),(8,8,8),(9,9,9),(10,10,10); +SET client_min_messages to debug1; +-- Generate series directly on the coordinator and on conflict do nothing +INSERT INTO target_table (col_1, col_2) +SELECT + s, s +FROM + generate_series(1,10) s +ON CONFLICT DO NOTHING; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- Test with multiple subqueries +INSERT INTO target_table +SELECT + col_1, col_2 +FROM ( + (SELECT + col_1, col_2, col_3 + FROM + source_table_1 + LIMIT 5) + UNION + (SELECT + col_1, col_2, col_3 + FROM + source_table_2 + 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: 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 +DEBUG: generating subplan XXX_2 for subquery SELECT col_1, col_2, col_3 FROM on_conflict.source_table_2 LIMIT 5 +DEBUG: generating subplan XXX_3 for subquery 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) UNION SELECT intermediate_result.col_1, intermediate_result.col_2, intermediate_result.col_3 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer) +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_3'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer, col_3 integer)) foo +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT * FROM target_table ORDER BY 1; + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 0 + 2 | 0 + 3 | 0 + 4 | 0 + 5 | 0 + 6 | 0 + 7 | 0 + 8 | 0 + 9 | 0 + 10 | 0 +(10 rows) + +WITH cte AS MATERIALIZED( + SELECT col_1, col_2, col_3 FROM source_table_1 +), cte_2 AS MATERIALIZED( + SELECT col_1, col_2 FROM cte +) +INSERT INTO target_table SELECT * FROM cte_2 ON CONFLICT(col_1) DO UPDATE SET col_2 = EXCLUDED.col_2 + 1; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: generating subplan XXX_1 for CTE cte: SELECT col_1, col_2, col_3 FROM on_conflict.source_table_1 +DEBUG: generating subplan XXX_2 for CTE cte_2: 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)) cte +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT col_1, col_2 FROM (SELECT cte_2.col_1, cte_2.col_2 FROM (SELECT intermediate_result.col_1, intermediate_result.col_2 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(col_1 integer, col_2 integer)) cte_2) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT * FROM target_table ORDER BY 1; + col_1 | col_2 +--------------------------------------------------------------------- + 1 | 2 + 2 | 3 + 3 | 4 + 4 | 5 + 5 | 6 + 6 | 0 + 7 | 0 + 8 | 0 + 9 | 0 + 10 | 0 +(10 rows) + +-- make sure that even if COPY switchover happens +-- the results are correct +SET citus.copy_switchover_threshold TO 1; +TRUNCATE target_table; +-- load some data to make sure copy commands switch over connections +INSERT INTO target_table SELECT i,0 FROM generate_series(0,500)i; +DEBUG: distributed INSERT ... SELECT can only select from distributed tables +DEBUG: Collecting INSERT ... SELECT results on coordinator +-- make sure that SELECT only uses 1 connection 1 node +-- yet still COPY commands use 1 connection per co-located +-- 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: push down of limit count: 10000 +DEBUG: Collecting INSERT ... SELECT results on coordinator +SELECT DISTINCT col_2 FROM target_table; + col_2 +--------------------------------------------------------------------- + 1 +(1 row) + +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: 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 + col_2 +--------------------------------------------------------------------- + 2 +(1 row) + +RESET client_min_messages; +DROP SCHEMA on_conflict CASCADE; +NOTICE: drop cascades to 7 other objects +DETAIL: drop cascades to table test_ref_table +drop cascades to table source_table_3 +drop cascades to table source_table_4 +drop cascades to table target_table_2 +drop cascades to table target_table +drop cascades to table source_table_1 +drop cascades to table source_table_2 diff --git a/src/test/regress/expected/multi_metadata_sync.out b/src/test/regress/expected/multi_metadata_sync.out index 280a174da..79d5e4b86 100644 --- a/src/test/regress/expected/multi_metadata_sync.out +++ b/src/test/regress/expected/multi_metadata_sync.out @@ -1,6 +1,16 @@ -- -- MULTI_METADATA_SYNC -- +-- this test has different output for PG13/14 compared to PG15 +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + -- Tests for metadata snapshot functions, metadata syncing functions and propagation of -- metadata changes to MX tables. -- Turn metadata sync off at first @@ -19,6 +29,7 @@ NOTICE: dropping metadata on the node (localhost,57638) (1 row) ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 1310000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 2; SET citus.replicate_reference_tables_on_activate TO off; SELECT nextval('pg_catalog.pg_dist_placement_placementid_seq') AS last_placement_id \gset @@ -59,10 +70,10 @@ 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; - CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + 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_dist_node @@ -70,9 +81,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard GRANT CREATE ON SCHEMA public TO PUBLIC; - GRANT CREATE ON SCHEMA public TO postgres; + GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; - GRANT USAGE ON SCHEMA public TO postgres; + 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) RESET ROLE RESET ROLE @@ -80,8 +91,8 @@ SELECT unnest(activate_node_snapshot()) order by 1; SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition 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_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition - SET ROLE postgres - SET ROLE postgres + SET ROLE pg_database_owner + SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' @@ -89,9 +100,8 @@ 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 - WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (1, 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 ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('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; -(30 rows) +(29 rows) -- this function is dropped in Citus10, added here for tests SET citus.enable_metadata_sync TO OFF; @@ -131,7 +141,7 @@ 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 - CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + 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)) DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object @@ -140,9 +150,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard GRANT CREATE ON SCHEMA public TO PUBLIC; - GRANT CREATE ON SCHEMA public TO postgres; + GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; - GRANT USAGE ON SCHEMA public TO postgres; + 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) RESET ROLE RESET ROLE @@ -155,8 +165,8 @@ SELECT unnest(activate_node_snapshot()) order by 1; 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_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition - SET ROLE postgres - SET ROLE postgres + SET ROLE pg_database_owner + SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' @@ -164,7 +174,7 @@ 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 - WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (1, 1, -1, 0, NULL, NULL), (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 (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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('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 placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; @@ -181,7 +191,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; 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 CREATE INDEX mx_index ON public.mx_test_table USING btree (col_2) - CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + 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)) DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object @@ -190,9 +200,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard GRANT CREATE ON SCHEMA public TO PUBLIC; - GRANT CREATE ON SCHEMA public TO postgres; + GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; - GRANT USAGE ON SCHEMA public TO postgres; + 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) RESET ROLE RESET ROLE @@ -205,8 +215,8 @@ SELECT unnest(activate_node_snapshot()) order by 1; 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_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition - SET ROLE postgres - SET ROLE postgres + SET ROLE pg_database_owner + SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' @@ -214,7 +224,7 @@ 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 - WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (1, 1, -1, 0, NULL, NULL), (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 (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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('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 placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; @@ -233,7 +243,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; ALTER TABLE mx_testing_schema.mx_test_table OWNER TO postgres CREATE INDEX mx_index ON mx_testing_schema.mx_test_table USING btree (col_2) CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres - CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION pg_database_owner 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)) DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object @@ -242,9 +252,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard GRANT CREATE ON SCHEMA public TO PUBLIC; - GRANT CREATE ON SCHEMA public TO postgres; + GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; - GRANT USAGE ON SCHEMA public TO postgres; + 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) RESET ROLE RESET ROLE @@ -257,8 +267,8 @@ SELECT unnest(activate_node_snapshot()) order by 1; 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('mx_testing_schema.mx_test_table') SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition - SET ROLE postgres - SET ROLE postgres + SET ROLE pg_database_owner + SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' @@ -266,7 +276,7 @@ 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 - WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (1, 1, -1, 0, NULL, NULL), (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 (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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; @@ -291,7 +301,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; ALTER TABLE mx_testing_schema.mx_test_table OWNER TO postgres CREATE INDEX mx_index ON mx_testing_schema.mx_test_table USING btree (col_2) CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres - CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION pg_database_owner 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)) DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object @@ -300,9 +310,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard GRANT CREATE ON SCHEMA public TO PUBLIC; - GRANT CREATE ON SCHEMA public TO postgres; + GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; - GRANT USAGE ON SCHEMA public TO postgres; + 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) RESET ROLE RESET ROLE @@ -315,8 +325,8 @@ SELECT unnest(activate_node_snapshot()) order by 1; 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('mx_testing_schema.mx_test_table') SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition - SET ROLE postgres - SET ROLE postgres + SET ROLE pg_database_owner + SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' @@ -324,7 +334,7 @@ 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 - WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (1, 1, -1, 0, NULL, NULL), (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 (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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; @@ -342,7 +352,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; ALTER TABLE mx_testing_schema.mx_test_table OWNER TO postgres CREATE INDEX mx_index ON mx_testing_schema.mx_test_table USING btree (col_2) CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres - CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION pg_database_owner 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)) DELETE FROM pg_catalog.pg_dist_colocation DELETE FROM pg_catalog.pg_dist_object @@ -351,9 +361,9 @@ SELECT unnest(activate_node_snapshot()) order by 1; DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard GRANT CREATE ON SCHEMA public TO PUBLIC; - GRANT CREATE ON SCHEMA public TO postgres; + GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; - GRANT USAGE ON SCHEMA public TO postgres; + 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) RESET ROLE RESET ROLE @@ -366,8 +376,8 @@ SELECT unnest(activate_node_snapshot()) order by 1; 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('mx_testing_schema.mx_test_table') SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition - SET ROLE postgres - SET ROLE postgres + SET ROLE pg_database_owner + SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' @@ -375,7 +385,7 @@ 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 - WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (1, 1, -1, 0, NULL, NULL), (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 (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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; @@ -519,9 +529,8 @@ SELECT "Column", "Type", "Definition" FROM index_attrs WHERE SELECT * FROM pg_dist_colocation ORDER BY colocationid; colocationid | shardcount | replicationfactor | distributioncolumntype | distributioncolumncollation --------------------------------------------------------------------- - 1 | 1 | -1 | 0 | 0 2 | 8 | 1 | 23 | 0 -(2 rows) +(1 row) -- Make sure that truncate trigger has been set for the MX table on worker SELECT count(*) FROM pg_trigger WHERE tgrelid='mx_testing_schema.mx_test_table'::regclass; @@ -1868,7 +1877,7 @@ SELECT unnest(activate_node_snapshot()) order by 1; CREATE SCHEMA IF NOT EXISTS mx_test_schema_2 AUTHORIZATION postgres CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres CREATE SCHEMA IF NOT EXISTS mx_testing_schema_2 AUTHORIZATION postgres - CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION pg_database_owner CREATE TABLE mx_test_schema_1.mx_table_1 (col1 integer, col2 text, col3 integer) CREATE TABLE mx_test_schema_2.mx_table_2 (col1 integer, col2 text) 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)) @@ -1882,19 +1891,19 @@ SELECT unnest(activate_node_snapshot()) order by 1; DELETE FROM pg_dist_placement DELETE FROM pg_dist_shard GRANT CREATE ON SCHEMA public TO PUBLIC; - GRANT CREATE ON SCHEMA public TO postgres; + GRANT CREATE ON SCHEMA public TO pg_database_owner; GRANT USAGE ON SCHEMA public TO PUBLIC; - GRANT USAGE ON SCHEMA public TO postgres; + 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) 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', 4, 's') - SELECT citus_internal_add_partition_metadata ('mx_test_schema_2.mx_table_2'::regclass, 'h', 'col1', 4, 's') + 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_testing_schema.mx_test_table'::regclass, 'h', 'col_1', 2, 's') - SELECT citus_internal_add_partition_metadata ('public.dist_table_1'::regclass, 'h', 'a', 10005, 's') - SELECT citus_internal_add_partition_metadata ('public.mx_ref'::regclass, 'n', NULL, 10003, 't') - SELECT citus_internal_add_partition_metadata ('public.test_table'::regclass, 'h', 'id', 10005, '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(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') @@ -1909,8 +1918,8 @@ SELECT unnest(activate_node_snapshot()) order by 1; SELECT worker_create_truncate_trigger('public.mx_ref') SELECT worker_create_truncate_trigger('public.test_table') SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition - SET ROLE postgres - SET ROLE postgres + SET ROLE pg_database_owner + SET ROLE pg_database_owner SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' SET citus.enable_ddl_propagation TO 'off' @@ -1918,7 +1927,7 @@ 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 - WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (10002, 7, 1, 'integer'::regtype, NULL, NULL), (10003, 1, -1, 0, NULL, NULL), (10004, 3, 1, 'integer'::regtype, NULL, NULL), (10005, 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 colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (10009, 1, -1, 0, NULL, NULL), (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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_test_schema_1', 'mx_table_1']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_test_schema_2', 'mx_table_2']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'mx_ref']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'dist_table_1']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_sequence_0']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_sequence_1']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema_2']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_test_schema_1']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_test_schema_2']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 5, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 5, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 5, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 5, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, shardlength, groupid, placementid) FROM placement_data; WITH placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310020, 1, 0, 1, 100020), (1310021, 1, 0, 5, 100021), (1310022, 1, 0, 1, 100022), (1310023, 1, 0, 5, 100023), (1310024, 1, 0, 1, 100024)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, shardlength, groupid, placementid) FROM placement_data; diff --git a/src/test/regress/expected/multi_metadata_sync_0.out b/src/test/regress/expected/multi_metadata_sync_0.out new file mode 100644 index 000000000..c5f4372bf --- /dev/null +++ b/src/test/regress/expected/multi_metadata_sync_0.out @@ -0,0 +1,2141 @@ +-- +-- MULTI_METADATA_SYNC +-- +-- this test has different output for PG13/14 compared to PG15 +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +-- Tests for metadata snapshot functions, metadata syncing functions and propagation of +-- metadata changes to MX tables. +-- Turn metadata sync off at first +SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); +NOTICE: dropping metadata on the node (localhost,57637) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT stop_metadata_sync_to_node('localhost', :worker_2_port); +NOTICE: dropping metadata on the node (localhost,57638) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 1310000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 2; +SET citus.replicate_reference_tables_on_activate TO off; +SELECT nextval('pg_catalog.pg_dist_placement_placementid_seq') AS last_placement_id +\gset +ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART 100000; +SELECT nextval('pg_catalog.pg_dist_groupid_seq') AS last_group_id \gset +SELECT nextval('pg_catalog.pg_dist_node_nodeid_seq') AS last_node_id \gset +-- Create the necessary test utility function +SET citus.enable_metadata_sync TO OFF; +CREATE FUNCTION activate_node_snapshot() + RETURNS text[] + LANGUAGE C STRICT + AS 'citus'; +RESET citus.enable_metadata_sync; +COMMENT ON FUNCTION activate_node_snapshot() + IS 'commands to activate node snapshot'; +-- Show that none of the existing tables are qualified to be MX tables +SELECT * FROM pg_dist_partition WHERE partmethod='h' AND repmodel='s'; + logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted +--------------------------------------------------------------------- +(0 rows) + +-- Since password_encryption default has been changed to sha from md5 with PG14 +-- we are updating it manually just for consistent test results between PG versions. +ALTER SYSTEM SET password_encryption TO md5; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(0.1); + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +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 +--------------------------------------------------------------------- + ALTER DATABASE regression OWNER TO postgres; + 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_dist_node + DELETE FROM pg_dist_partition + DELETE FROM pg_dist_placement + DELETE FROM pg_dist_shard + 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) + RESET ROLE + RESET ROLE + SELECT alter_role_if_exists('postgres', 'ALTER ROLE postgres SET lc_messages = ''C''') + SELECT pg_catalog.worker_drop_sequence_dependency(logicalrelid::regclass::text) FROM pg_dist_partition + 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_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition + SET ROLE postgres + SET ROLE postgres + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + UPDATE pg_dist_local_group SET groupid = 1 + WITH distributed_object_data(typetext, objnames, objargs, distargumentindex, colocationid, force_delegation) AS (VALUES ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('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; +(29 rows) + +-- this function is dropped in Citus10, added here for tests +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE FUNCTION pg_catalog.master_create_distributed_table(table_name regclass, + distribution_column text, + distribution_method citus.distribution_type) + RETURNS void + LANGUAGE C STRICT + AS 'citus', $$master_create_distributed_table$$; +RESET citus.enable_metadata_sync; +COMMENT ON FUNCTION pg_catalog.master_create_distributed_table(table_name regclass, + distribution_column text, + distribution_method citus.distribution_type) + IS 'define the table distribution functions'; +-- Create a test table with constraints and SERIAL and default from user defined sequence +CREATE SEQUENCE user_defined_seq; +CREATE TABLE mx_test_table (col_1 int UNIQUE, col_2 text NOT NULL, col_3 BIGSERIAL, col_4 BIGINT DEFAULT nextval('user_defined_seq')); +set citus.shard_count to 8; +set citus.shard_replication_factor to 1; +SELECT create_distributed_table('mx_test_table', 'col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(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; +-- 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 +--------------------------------------------------------------------- + ALTER DATABASE regression OWNER TO postgres; + ALTER SEQUENCE public.mx_test_table_col_3_seq OWNER TO postgres + 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 + 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)) + DELETE FROM pg_catalog.pg_dist_colocation + DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_dist_node + DELETE FROM pg_dist_partition + DELETE FROM pg_dist_placement + DELETE FROM pg_dist_shard + 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) + 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(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_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition + SET ROLE postgres + SET ROLE postgres + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + UPDATE pg_dist_local_group SET groupid = 1 + 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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('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 placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; +(42 rows) + +-- 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; + unnest +--------------------------------------------------------------------- + ALTER DATABASE regression OWNER TO postgres; + ALTER SEQUENCE public.mx_test_table_col_3_seq OWNER TO postgres + 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 + CREATE INDEX mx_index ON public.mx_test_table USING btree (col_2) + 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)) + DELETE FROM pg_catalog.pg_dist_colocation + DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_dist_node + DELETE FROM pg_dist_partition + DELETE FROM pg_dist_placement + DELETE FROM pg_dist_shard + 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) + 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(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_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition + SET ROLE postgres + SET ROLE postgres + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + UPDATE pg_dist_local_group SET groupid = 1 + 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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('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 placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; +(43 rows) + +-- Show that schema changes are included in the activate node snapshot +CREATE SCHEMA mx_testing_schema; +ALTER TABLE mx_test_table SET SCHEMA mx_testing_schema; +SELECT unnest(activate_node_snapshot()) order by 1; + unnest +--------------------------------------------------------------------- + ALTER DATABASE regression OWNER TO postgres; + ALTER SEQUENCE mx_testing_schema.mx_test_table_col_3_seq OWNER TO postgres + ALTER SEQUENCE public.user_defined_seq OWNER TO postgres + ALTER TABLE mx_testing_schema.mx_test_table ADD CONSTRAINT mx_test_table_col_1_key UNIQUE (col_1) + ALTER TABLE mx_testing_schema.mx_test_table OWNER TO postgres + CREATE INDEX mx_index ON mx_testing_schema.mx_test_table USING btree (col_2) + CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + 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)) + DELETE FROM pg_catalog.pg_dist_colocation + DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_dist_node + DELETE FROM pg_dist_partition + DELETE FROM pg_dist_placement + DELETE FROM pg_dist_shard + 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) + 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(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') + 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('mx_testing_schema.mx_test_table') + SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition + SET ROLE postgres + SET ROLE postgres + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + UPDATE pg_dist_local_group SET groupid = 1 + 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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; +(44 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); +SELECT master_create_distributed_table('non_mx_test_table', 'col_1', 'append'); + master_create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +UPDATE pg_dist_partition SET repmodel='s' WHERE logicalrelid='non_mx_test_table'::regclass; +SELECT unnest(activate_node_snapshot()) order by 1; + unnest +--------------------------------------------------------------------- + ALTER DATABASE regression OWNER TO postgres; + ALTER SEQUENCE mx_testing_schema.mx_test_table_col_3_seq OWNER TO postgres + ALTER SEQUENCE public.user_defined_seq OWNER TO postgres + ALTER TABLE mx_testing_schema.mx_test_table ADD CONSTRAINT mx_test_table_col_1_key UNIQUE (col_1) + ALTER TABLE mx_testing_schema.mx_test_table OWNER TO postgres + CREATE INDEX mx_index ON mx_testing_schema.mx_test_table USING btree (col_2) + CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + 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)) + DELETE FROM pg_catalog.pg_dist_colocation + DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_dist_node + DELETE FROM pg_dist_partition + DELETE FROM pg_dist_placement + DELETE FROM pg_dist_shard + 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) + 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(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') + 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('mx_testing_schema.mx_test_table') + SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition + SET ROLE postgres + SET ROLE postgres + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + UPDATE pg_dist_local_group SET groupid = 1 + 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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; +(44 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; +SELECT unnest(activate_node_snapshot()) order by 1; + unnest +--------------------------------------------------------------------- + ALTER DATABASE regression OWNER TO postgres; + ALTER SEQUENCE mx_testing_schema.mx_test_table_col_3_seq OWNER TO postgres + ALTER SEQUENCE public.user_defined_seq OWNER TO postgres + ALTER TABLE mx_testing_schema.mx_test_table ADD CONSTRAINT mx_test_table_col_1_key UNIQUE (col_1) + ALTER TABLE mx_testing_schema.mx_test_table OWNER TO postgres + CREATE INDEX mx_index ON mx_testing_schema.mx_test_table USING btree (col_2) + CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + 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)) + DELETE FROM pg_catalog.pg_dist_colocation + DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_dist_node + DELETE FROM pg_dist_partition + DELETE FROM pg_dist_placement + DELETE FROM pg_dist_shard + 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) + 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(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') + 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('mx_testing_schema.mx_test_table') + SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition + SET ROLE postgres + SET ROLE postgres + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + UPDATE pg_dist_local_group SET groupid = 1 + 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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 2, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 2, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 2, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 2, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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; +(44 rows) + +-- Test start_metadata_sync_to_node and citus_activate_node UDFs +-- Ensure that hasmetadata=false for all nodes +SELECT count(*) FROM pg_dist_node WHERE hasmetadata=true; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- Show that metadata can not be synced on secondary node +SELECT groupid AS worker_1_group FROM pg_dist_node WHERE nodeport = :worker_1_port \gset +SELECT master_add_node('localhost', 8888, groupid => :worker_1_group, noderole => 'secondary'); + master_add_node +--------------------------------------------------------------------- + 4 +(1 row) + +SELECT start_metadata_sync_to_node('localhost', 8888); + start_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT hasmetadata FROM pg_dist_node WHERE nodeport = 8888; + hasmetadata +--------------------------------------------------------------------- + f +(1 row) + +SELECT stop_metadata_sync_to_node('localhost', 8888); +NOTICE: (localhost,8888) is a secondary node: to clear the metadata, you should clear metadata from the primary node + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT hasmetadata FROM pg_dist_node WHERE nodeport = 8888; + hasmetadata +--------------------------------------------------------------------- + f +(1 row) + +-- Add a node to another cluster to make sure it's also synced +SELECT master_add_secondary_node('localhost', 8889, 'localhost', :worker_1_port, nodecluster => 'second-cluster'); + master_add_secondary_node +--------------------------------------------------------------------- + 5 +(1 row) + +\c - - - :master_port +-- Run start_metadata_sync_to_node and citus_activate_node and check that it marked hasmetadata for that worker +SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT nodeid, hasmetadata FROM pg_dist_node WHERE nodename='localhost' AND nodeport=:worker_1_port; + nodeid | hasmetadata +--------------------------------------------------------------------- + 1 | t +(1 row) + +-- Check that the metadata has been copied to the worker +\c - - - :worker_1_port +SELECT * FROM pg_dist_local_group; + groupid +--------------------------------------------------------------------- + 1 +(1 row) + +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) + +SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY logicalrelid; + logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted +--------------------------------------------------------------------- + mx_testing_schema.mx_test_table | h | {VAR :varno 1 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 2 | s | f +(1 row) + +SELECT * FROM pg_dist_shard WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY shardid; + logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue +--------------------------------------------------------------------- + mx_testing_schema.mx_test_table | 1310000 | t | -2147483648 | -1610612737 + mx_testing_schema.mx_test_table | 1310001 | t | -1610612736 | -1073741825 + mx_testing_schema.mx_test_table | 1310002 | t | -1073741824 | -536870913 + mx_testing_schema.mx_test_table | 1310003 | t | -536870912 | -1 + mx_testing_schema.mx_test_table | 1310004 | t | 0 | 536870911 + mx_testing_schema.mx_test_table | 1310005 | t | 536870912 | 1073741823 + mx_testing_schema.mx_test_table | 1310006 | t | 1073741824 | 1610612735 + mx_testing_schema.mx_test_table | 1310007 | t | 1610612736 | 2147483647 +(8 rows) + +SELECT * FROM pg_dist_shard_placement WHERE shardid IN (SELECT shardid FROM pg_dist_shard WHERE logicalrelid::text LIKE 'mx_testing_schema%') ORDER BY shardid, nodename, nodeport; + shardid | shardstate | shardlength | nodename | nodeport | placementid +--------------------------------------------------------------------- + 1310000 | 1 | 0 | localhost | 57637 | 100000 + 1310001 | 1 | 0 | localhost | 57638 | 100001 + 1310002 | 1 | 0 | localhost | 57637 | 100002 + 1310003 | 1 | 0 | localhost | 57638 | 100003 + 1310004 | 1 | 0 | localhost | 57637 | 100004 + 1310005 | 1 | 0 | localhost | 57638 | 100005 + 1310006 | 1 | 0 | localhost | 57637 | 100006 + 1310007 | 1 | 0 | localhost | 57638 | 100007 +(8 rows) + +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_testing_schema.mx_test_table'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + col_1 | integer | + col_2 | text | not null + col_3 | bigint | not null default nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) + col_4 | bigint | default nextval('user_defined_seq'::regclass) +(4 rows) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_testing_schema.mx_test_table_col_1_key'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col_1 | integer | col_1 +(1 row) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_testing_schema.mx_index'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col_2 | text | col_2 +(1 row) + +-- Check that pg_dist_colocation is synced +SELECT * FROM pg_dist_colocation ORDER BY colocationid; + colocationid | shardcount | replicationfactor | distributioncolumntype | distributioncolumncollation +--------------------------------------------------------------------- + 2 | 8 | 1 | 23 | 0 +(1 row) + +-- Make sure that truncate trigger has been set for the MX table on worker +SELECT count(*) FROM pg_trigger WHERE tgrelid='mx_testing_schema.mx_test_table'::regclass; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- Make sure that citus_activate_node considers foreign key constraints +\c - - - :master_port +-- Since we're superuser, we can set the replication model to 'streaming' to +-- create some MX tables +SET citus.shard_replication_factor TO 1; +CREATE SCHEMA mx_testing_schema_2; +CREATE TABLE mx_testing_schema.fk_test_1 (col1 int, col2 text, col3 int, UNIQUE(col1, col3)); +CREATE TABLE mx_testing_schema_2.fk_test_2 (col1 int, col2 int, col3 text, + FOREIGN KEY (col1, col2) REFERENCES mx_testing_schema.fk_test_1 (col1, col3)); +SELECT create_distributed_table('mx_testing_schema.fk_test_1', 'col1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('mx_testing_schema_2.fk_test_2', 'col1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- Check that foreign key metadata exists on the worker +\c - - - :worker_1_port +SELECT "Constraint", "Definition" FROM table_fkeys WHERE relid='mx_testing_schema_2.fk_test_2'::regclass; + Constraint | Definition +--------------------------------------------------------------------- + fk_test_2_col1_col2_fkey | FOREIGN KEY (col1, col2) REFERENCES mx_testing_schema.fk_test_1(col1, col3) +(1 row) + +\c - - - :master_port +DROP TABLE mx_testing_schema_2.fk_test_2; +DROP TABLE mx_testing_schema.fk_test_1; +RESET citus.shard_replication_factor; +-- Check that repeated calls to citus_activate_node has no side effects +\c - - - :master_port +SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +\c - - - :worker_1_port +SELECT * FROM pg_dist_local_group; + groupid +--------------------------------------------------------------------- + 1 +(1 row) + +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) + +SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY logicalrelid; + logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted +--------------------------------------------------------------------- + mx_testing_schema.mx_test_table | h | {VAR :varno 1 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 2 | s | f +(1 row) + +SELECT * FROM pg_dist_shard WHERE logicalrelid::text LIKE 'mx_testing_schema%' ORDER BY shardid; + logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue +--------------------------------------------------------------------- + mx_testing_schema.mx_test_table | 1310000 | t | -2147483648 | -1610612737 + mx_testing_schema.mx_test_table | 1310001 | t | -1610612736 | -1073741825 + mx_testing_schema.mx_test_table | 1310002 | t | -1073741824 | -536870913 + mx_testing_schema.mx_test_table | 1310003 | t | -536870912 | -1 + mx_testing_schema.mx_test_table | 1310004 | t | 0 | 536870911 + mx_testing_schema.mx_test_table | 1310005 | t | 536870912 | 1073741823 + mx_testing_schema.mx_test_table | 1310006 | t | 1073741824 | 1610612735 + mx_testing_schema.mx_test_table | 1310007 | t | 1610612736 | 2147483647 +(8 rows) + +SELECT * FROM pg_dist_shard_placement WHERE shardid IN (SELECT shardid FROM pg_dist_shard WHERE logicalrelid::text LIKE 'mx_testing_schema%') ORDER BY shardid, nodename, nodeport; + shardid | shardstate | shardlength | nodename | nodeport | placementid +--------------------------------------------------------------------- + 1310000 | 1 | 0 | localhost | 57637 | 100000 + 1310001 | 1 | 0 | localhost | 57638 | 100001 + 1310002 | 1 | 0 | localhost | 57637 | 100002 + 1310003 | 1 | 0 | localhost | 57638 | 100003 + 1310004 | 1 | 0 | localhost | 57637 | 100004 + 1310005 | 1 | 0 | localhost | 57638 | 100005 + 1310006 | 1 | 0 | localhost | 57637 | 100006 + 1310007 | 1 | 0 | localhost | 57638 | 100007 +(8 rows) + +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_testing_schema.mx_test_table'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + col_1 | integer | + col_2 | text | not null + col_3 | bigint | not null default nextval('mx_testing_schema.mx_test_table_col_3_seq'::regclass) + col_4 | bigint | default nextval('user_defined_seq'::regclass) +(4 rows) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_testing_schema.mx_test_table_col_1_key'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col_1 | integer | col_1 +(1 row) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_testing_schema.mx_index'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col_2 | text | col_2 +(1 row) + +SELECT count(*) FROM pg_trigger WHERE tgrelid='mx_testing_schema.mx_test_table'::regclass; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- Make sure that citus_activate_node can be called inside a transaction and rollbacked +\c - - - :master_port +BEGIN; +SELECT 1 FROM citus_activate_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +ROLLBACK; +SELECT hasmetadata FROM pg_dist_node WHERE nodeport=:worker_2_port; + hasmetadata +--------------------------------------------------------------------- + f +(1 row) + +-- Check that the distributed table can be queried from the worker +\c - - - :master_port +SET citus.shard_replication_factor TO 1; +SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE TABLE mx_query_test (a int, b text, c int); +SELECT create_distributed_table('mx_query_test', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT repmodel FROM pg_dist_partition WHERE logicalrelid='mx_query_test'::regclass; + repmodel +--------------------------------------------------------------------- + s +(1 row) + +INSERT INTO mx_query_test VALUES (1, 'one', 1); +INSERT INTO mx_query_test VALUES (2, 'two', 4); +INSERT INTO mx_query_test VALUES (3, 'three', 9); +INSERT INTO mx_query_test VALUES (4, 'four', 16); +INSERT INTO mx_query_test VALUES (5, 'five', 24); +\c - - - :worker_1_port +SELECT * FROM mx_query_test ORDER BY a; + a | b | c +--------------------------------------------------------------------- + 1 | one | 1 + 2 | two | 4 + 3 | three | 9 + 4 | four | 16 + 5 | five | 24 +(5 rows) + +INSERT INTO mx_query_test VALUES (6, 'six', 36); +UPDATE mx_query_test SET c = 25 WHERE a = 5; +\c - - - :master_port +SELECT * FROM mx_query_test ORDER BY a; + a | b | c +--------------------------------------------------------------------- + 1 | one | 1 + 2 | two | 4 + 3 | three | 9 + 4 | four | 16 + 5 | five | 25 + 6 | six | 36 +(6 rows) + +\c - - - :master_port +DROP TABLE mx_query_test; +-- 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); + start_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT hasmetadata FROM pg_dist_node WHERE nodeport=:worker_1_port; + hasmetadata +--------------------------------------------------------------------- + t +(1 row) + +SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); +NOTICE: dropping metadata on the node (localhost,57637) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT hasmetadata FROM pg_dist_node WHERE nodeport=:worker_1_port; + hasmetadata +--------------------------------------------------------------------- + f +(1 row) + +-- Test DDL propagation in MX tables +SELECT start_metadata_sync_to_node('localhost', :worker_1_port); + start_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SET citus.shard_count = 5; +CREATE SCHEMA mx_test_schema_1; +CREATE SCHEMA mx_test_schema_2; +-- Create MX tables +SET citus.shard_replication_factor TO 1; +CREATE TABLE mx_test_schema_1.mx_table_1 (col1 int UNIQUE, col2 text); +CREATE INDEX mx_index_1 ON mx_test_schema_1.mx_table_1 (col1); +CREATE TABLE mx_test_schema_2.mx_table_2 (col1 int, col2 text); +CREATE INDEX mx_index_2 ON mx_test_schema_2.mx_table_2 (col2); +ALTER TABLE mx_test_schema_2.mx_table_2 ADD CONSTRAINT mx_fk_constraint FOREIGN KEY(col1) REFERENCES mx_test_schema_1.mx_table_1(col1); +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_test_schema_1.mx_table_1'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + col1 | integer | + col2 | text | +(2 rows) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_test_schema_1.mx_table_1_col1_key'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col1 | integer | col1 +(1 row) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_test_schema_1.mx_index_1'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col1 | integer | col1 +(1 row) + +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_test_schema_2.mx_table_2'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + col1 | integer | + col2 | text | +(2 rows) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_test_schema_2.mx_index_2'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col2 | text | col2 +(1 row) + +SELECT "Constraint", "Definition" FROM table_fkeys WHERE relid='mx_test_schema_2.mx_table_2'::regclass; + Constraint | Definition +--------------------------------------------------------------------- + mx_fk_constraint | FOREIGN KEY (col1) REFERENCES mx_test_schema_1.mx_table_1(col1) +(1 row) + +SELECT create_distributed_table('mx_test_schema_1.mx_table_1', 'col1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('mx_test_schema_2.mx_table_2', 'col1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- Check that created tables are marked as streaming replicated tables +SELECT + logicalrelid, repmodel +FROM + pg_dist_partition +WHERE + logicalrelid = 'mx_test_schema_1.mx_table_1'::regclass + OR logicalrelid = 'mx_test_schema_2.mx_table_2'::regclass +ORDER BY + logicalrelid; + logicalrelid | repmodel +--------------------------------------------------------------------- + mx_test_schema_1.mx_table_1 | s + mx_test_schema_2.mx_table_2 | s +(2 rows) + +-- See the shards and placements of the mx tables +SELECT + logicalrelid, shardid, nodename, nodeport +FROM + pg_dist_shard NATURAL JOIN pg_dist_shard_placement +WHERE + logicalrelid = 'mx_test_schema_1.mx_table_1'::regclass + OR logicalrelid = 'mx_test_schema_2.mx_table_2'::regclass +ORDER BY + logicalrelid, 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_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 +(10 rows) + +-- Check that metadata of MX tables exist on the metadata worker +\c - - - :worker_1_port +-- Check that tables are created +\dt mx_test_schema_?.mx_table_? + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + mx_test_schema_1 | mx_table_1 | table | postgres + mx_test_schema_2 | mx_table_2 | table | postgres +(2 rows) + +-- Check that table metadata are created +SELECT + logicalrelid, repmodel +FROM + pg_dist_partition +WHERE + logicalrelid = 'mx_test_schema_1.mx_table_1'::regclass + OR logicalrelid = 'mx_test_schema_2.mx_table_2'::regclass; + logicalrelid | repmodel +--------------------------------------------------------------------- + mx_test_schema_1.mx_table_1 | s + mx_test_schema_2.mx_table_2 | s +(2 rows) + +-- Check that shard and placement data are created +SELECT + logicalrelid, shardid, nodename, nodeport +FROM + pg_dist_shard NATURAL JOIN pg_dist_shard_placement +WHERE + logicalrelid = 'mx_test_schema_1.mx_table_1'::regclass + OR logicalrelid = 'mx_test_schema_2.mx_table_2'::regclass +ORDER BY + logicalrelid, 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_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 +(10 rows) + +-- Check that metadata of MX tables don't exist on the non-metadata worker +\c - - - :worker_2_port +\d mx_test_schema_1.mx_table_1 +\d mx_test_schema_2.mx_table_2 +SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'mx_test_schema%'; + logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM pg_dist_shard WHERE logicalrelid::text LIKE 'mx_test_schema%'; + logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM pg_dist_shard_placement ORDER BY shardid, nodename, nodeport; + shardid | shardstate | shardlength | nodename | nodeport | placementid +--------------------------------------------------------------------- +(0 rows) + +-- Check that CREATE INDEX statement is propagated +\c - - - :master_port +SET client_min_messages TO 'ERROR'; +CREATE INDEX mx_index_3 ON mx_test_schema_2.mx_table_2 USING hash (col1); +ALTER TABLE mx_test_schema_2.mx_table_2 ADD CONSTRAINT mx_table_2_col1_key UNIQUE (col1); +\c - - - :worker_1_port +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_test_schema_2.mx_index_3'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col1 | integer | col1 +(1 row) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_test_schema_2.mx_table_2_col1_key'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col1 | integer | col1 +(1 row) + +-- Check that DROP INDEX statement is propagated +\c - - - :master_port +DROP INDEX mx_test_schema_2.mx_index_3; +\c - - - :worker_1_port +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_test_schema_2.mx_index_3'::regclass; +ERROR: relation "mx_test_schema_2.mx_index_3" does not exist +-- Check that ALTER TABLE statements are propagated +\c - - - :master_port +ALTER TABLE mx_test_schema_1.mx_table_1 ADD COLUMN col3 NUMERIC; +ALTER TABLE mx_test_schema_1.mx_table_1 ALTER COLUMN col3 SET DATA TYPE INT; +ALTER TABLE + mx_test_schema_1.mx_table_1 +ADD CONSTRAINT + mx_fk_constraint +FOREIGN KEY + (col1) +REFERENCES + mx_test_schema_2.mx_table_2(col1); +\c - - - :worker_1_port +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_test_schema_1.mx_table_1'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + col1 | integer | + col2 | text | + col3 | integer | +(3 rows) + +SELECT "Constraint", "Definition" FROM table_fkeys WHERE relid='mx_test_schema_1.mx_table_1'::regclass; + Constraint | Definition +--------------------------------------------------------------------- + mx_fk_constraint | FOREIGN KEY (col1) REFERENCES mx_test_schema_2.mx_table_2(col1) +(1 row) + +-- Check that foreign key constraint with NOT VALID works as well +\c - - - :master_port +ALTER TABLE mx_test_schema_1.mx_table_1 DROP CONSTRAINT mx_fk_constraint; +ALTER TABLE + mx_test_schema_1.mx_table_1 +ADD CONSTRAINT + mx_fk_constraint_2 +FOREIGN KEY + (col1) +REFERENCES + mx_test_schema_2.mx_table_2(col1) +NOT VALID; +\c - - - :worker_1_port +SELECT "Constraint", "Definition" FROM table_fkeys WHERE relid='mx_test_schema_1.mx_table_1'::regclass; + Constraint | Definition +--------------------------------------------------------------------- + mx_fk_constraint_2 | FOREIGN KEY (col1) REFERENCES mx_test_schema_2.mx_table_2(col1) +(1 row) + +-- Check that update_distributed_table_colocation call propagates the changes to the workers +\c - - - :master_port +SELECT nextval('pg_catalog.pg_dist_colocationid_seq') AS last_colocation_id \gset +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 10000; +SET citus.shard_count TO 7; +SET citus.shard_replication_factor TO 1; +CREATE TABLE mx_colocation_test_1 (a int); +SELECT create_distributed_table('mx_colocation_test_1', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE mx_colocation_test_2 (a int); +SELECT create_distributed_table('mx_colocation_test_2', 'a', colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- Reset the colocation IDs of the test tables +DELETE FROM + pg_dist_colocation +WHERE EXISTS ( + SELECT 1 + FROM pg_dist_partition + WHERE + colocationid = pg_dist_partition.colocationid + AND pg_dist_partition.logicalrelid = 'mx_colocation_test_1'::regclass); +-- Check the colocation IDs of the created tables +SELECT + logicalrelid, colocationid +FROM + pg_dist_partition +WHERE + logicalrelid = 'mx_colocation_test_1'::regclass + OR logicalrelid = 'mx_colocation_test_2'::regclass +ORDER BY logicalrelid; + logicalrelid | colocationid +--------------------------------------------------------------------- + mx_colocation_test_1 | 10000 + mx_colocation_test_2 | 10001 +(2 rows) + +-- Update colocation and see the changes on the master and the worker +SELECT update_distributed_table_colocation('mx_colocation_test_1', colocate_with => 'mx_colocation_test_2'); + update_distributed_table_colocation +--------------------------------------------------------------------- + +(1 row) + +SELECT + logicalrelid, colocationid +FROM + pg_dist_partition +WHERE + logicalrelid = 'mx_colocation_test_1'::regclass + OR logicalrelid = 'mx_colocation_test_2'::regclass; + logicalrelid | colocationid +--------------------------------------------------------------------- + mx_colocation_test_2 | 10001 + mx_colocation_test_1 | 10001 +(2 rows) + +\c - - - :worker_1_port +SELECT + logicalrelid, colocationid +FROM + pg_dist_partition +WHERE + logicalrelid = 'mx_colocation_test_1'::regclass + OR logicalrelid = 'mx_colocation_test_2'::regclass; + logicalrelid | colocationid +--------------------------------------------------------------------- + mx_colocation_test_2 | 10001 + mx_colocation_test_1 | 10001 +(2 rows) + +\c - - - :master_port +-- Check that DROP TABLE on MX tables works +DROP TABLE mx_colocation_test_1; +DROP TABLE mx_colocation_test_2; +\d mx_colocation_test_1 +\d mx_colocation_test_2 +\c - - - :worker_1_port +\d mx_colocation_test_1 +\d mx_colocation_test_2 +-- Check that dropped MX table can be recreated again +\c - - - :master_port +SET citus.shard_count TO 7; +SET citus.shard_replication_factor TO 1; +CREATE TABLE mx_temp_drop_test (a int); +SELECT create_distributed_table('mx_temp_drop_test', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT logicalrelid, repmodel FROM pg_dist_partition WHERE logicalrelid = 'mx_temp_drop_test'::regclass; + logicalrelid | repmodel +--------------------------------------------------------------------- + mx_temp_drop_test | s +(1 row) + +DROP TABLE mx_temp_drop_test; +CREATE TABLE mx_temp_drop_test (a int); +SELECT create_distributed_table('mx_temp_drop_test', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT logicalrelid, repmodel FROM pg_dist_partition WHERE logicalrelid = 'mx_temp_drop_test'::regclass; + logicalrelid | repmodel +--------------------------------------------------------------------- + mx_temp_drop_test | s +(1 row) + +DROP TABLE mx_temp_drop_test; +-- Check that MX tables can be created with SERIAL columns +\c - - - :master_port +SET citus.shard_count TO 3; +SET citus.shard_replication_factor TO 1; +SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); +NOTICE: dropping metadata on the node (localhost,57637) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT stop_metadata_sync_to_node('localhost', :worker_2_port); +NOTICE: dropping metadata on the node (localhost,57638) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +-- sync table with serial column after create_distributed_table +CREATE TABLE mx_table_with_small_sequence(a int, b SERIAL, c SMALLSERIAL); +SELECT create_distributed_table('mx_table_with_small_sequence', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +DROP TABLE mx_table_with_small_sequence; +-- Show that create_distributed_table works with a serial column +CREATE TABLE mx_table_with_small_sequence(a int, b SERIAL, c SMALLSERIAL); +SELECT create_distributed_table('mx_table_with_small_sequence', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO mx_table_with_small_sequence VALUES (0); +\c - - - :worker_1_port +-- Insert doesn't work because the defaults are of type int and smallint +INSERT INTO mx_table_with_small_sequence VALUES (1), (3); +ERROR: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +\c - - - :master_port +SET citus.shard_replication_factor TO 1; +-- Create an MX table with (BIGSERIAL) sequences +CREATE TABLE mx_table_with_sequence(a int, b BIGSERIAL, c BIGSERIAL); +SELECT create_distributed_table('mx_table_with_sequence', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO mx_table_with_sequence VALUES (0); +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_table_with_sequence'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + a | integer | + b | bigint | not null default nextval('mx_table_with_sequence_b_seq'::regclass) + c | bigint | not null default nextval('mx_table_with_sequence_c_seq'::regclass) +(3 rows) + +\ds mx_table_with_sequence_b_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_table_with_sequence_b_seq | sequence | postgres +(1 row) + +\ds mx_table_with_sequence_c_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_table_with_sequence_c_seq | sequence | postgres +(1 row) + +-- Check that the sequences created on the metadata worker as well +\c - - - :worker_1_port +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_table_with_sequence'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + a | integer | + b | bigint | not null default nextval('mx_table_with_sequence_b_seq'::regclass) + c | bigint | not null default nextval('mx_table_with_sequence_c_seq'::regclass) +(3 rows) + +\ds mx_table_with_sequence_b_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_table_with_sequence_b_seq | sequence | postgres +(1 row) + +\ds mx_table_with_sequence_c_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_table_with_sequence_c_seq | sequence | postgres +(1 row) + +-- Insert works because the defaults are of type bigint +INSERT INTO mx_table_with_sequence VALUES (1), (3); +-- check that pg_depend records exist on the worker +SELECT refobjsubid FROM pg_depend +WHERE objid = 'mx_table_with_sequence_b_seq'::regclass AND refobjid = 'mx_table_with_sequence'::regclass; + refobjsubid +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT refobjsubid FROM pg_depend +WHERE objid = 'mx_table_with_sequence_c_seq'::regclass AND refobjid = 'mx_table_with_sequence'::regclass; + refobjsubid +--------------------------------------------------------------------- + 3 +(1 row) + +-- Check that the sequences on the worker have their own space +SELECT nextval('mx_table_with_sequence_b_seq'); + nextval +--------------------------------------------------------------------- + 281474976710659 +(1 row) + +SELECT nextval('mx_table_with_sequence_c_seq'); + nextval +--------------------------------------------------------------------- + 281474976710659 +(1 row) + +-- Check that adding a new metadata node sets the sequence space correctly +\c - - - :master_port +SELECT 1 FROM citus_activate_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +\c - - - :worker_2_port +SELECT groupid FROM pg_dist_local_group; + groupid +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_table_with_sequence'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + a | integer | + b | bigint | not null default nextval('mx_table_with_sequence_b_seq'::regclass) + c | bigint | not null default nextval('mx_table_with_sequence_c_seq'::regclass) +(3 rows) + +\ds mx_table_with_sequence_b_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_table_with_sequence_b_seq | sequence | postgres +(1 row) + +\ds mx_table_with_sequence_c_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_table_with_sequence_c_seq | sequence | postgres +(1 row) + +SELECT nextval('mx_table_with_sequence_b_seq'); + nextval +--------------------------------------------------------------------- + 562949953421313 +(1 row) + +SELECT nextval('mx_table_with_sequence_c_seq'); + nextval +--------------------------------------------------------------------- + 562949953421313 +(1 row) + +-- Insert doesn't work because the defaults are of type int and smallint +INSERT INTO mx_table_with_small_sequence VALUES (2), (4); +ERROR: nextval(sequence) calls in worker nodes are not supported for column defaults of type int or smallint +-- Insert works because the defaults are of type bigint +INSERT INTO mx_table_with_sequence VALUES (2), (4); +-- Check that dropping the mx table with sequences works as expected +\c - - - :master_port +-- check our small sequence values +SELECT a, b, c FROM mx_table_with_small_sequence ORDER BY a,b,c; + a | b | c +--------------------------------------------------------------------- + 0 | 1 | 1 +(1 row) + +--check our bigint sequence values +SELECT a, b, c FROM mx_table_with_sequence ORDER BY a,b,c; + a | b | c +--------------------------------------------------------------------- + 0 | 1 | 1 + 1 | 281474976710657 | 281474976710657 + 2 | 562949953421314 | 562949953421314 + 3 | 281474976710658 | 281474976710658 + 4 | 562949953421315 | 562949953421315 +(5 rows) + +-- Check that dropping the mx table with sequences works as expected +DROP TABLE mx_table_with_small_sequence, mx_table_with_sequence; +\d mx_table_with_sequence +\ds mx_table_with_sequence_b_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- +(0 rows) + +\ds mx_table_with_sequence_c_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- +(0 rows) + +-- Check that the sequences are dropped from the workers +\c - - - :worker_1_port +\d mx_table_with_sequence +\ds mx_table_with_sequence_b_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- +(0 rows) + +\ds mx_table_with_sequence_c_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- +(0 rows) + +-- Check that the sequences are dropped from the workers +\c - - - :worker_2_port +\ds mx_table_with_sequence_b_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- +(0 rows) + +\ds mx_table_with_sequence_c_seq + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- +(0 rows) + +-- Check that MX sequences play well with non-super users +\c - - - :master_port +-- Remove a node so that shards and sequences won't be created on table creation. Therefore, +-- we can test that citus_activate_node can actually create the sequence with proper +-- owner +CREATE TABLE pg_dist_placement_temp AS SELECT * FROM pg_dist_placement; +CREATE TABLE pg_dist_partition_temp AS SELECT * FROM pg_dist_partition; +CREATE TABLE pg_dist_object_temp AS SELECT * FROM pg_catalog.pg_dist_object; +DELETE FROM pg_dist_placement; +DELETE FROM pg_dist_partition; +DELETE FROM pg_catalog.pg_dist_object; +SELECT groupid AS old_worker_2_group FROM pg_dist_node WHERE nodeport = :worker_2_port \gset +SELECT master_remove_node('localhost', :worker_2_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + + -- the master user needs superuser permissions to change the replication model +CREATE USER mx_user WITH SUPERUSER; +\c - mx_user - :master_port +-- Create an mx table as a different user +CREATE TABLE mx_table (a int, b BIGSERIAL); +SET citus.shard_replication_factor TO 1; +SELECT create_distributed_table('mx_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +\c - postgres - :master_port +SELECT master_add_node('localhost', :worker_2_port); + master_add_node +--------------------------------------------------------------------- + 6 +(1 row) + +\c - mx_user - :worker_1_port +SELECT nextval('mx_table_b_seq'); + nextval +--------------------------------------------------------------------- + 281474976710657 +(1 row) + +INSERT INTO mx_table (a) VALUES (37); +INSERT INTO mx_table (a) VALUES (38); +SELECT * FROM mx_table ORDER BY a; + a | b +--------------------------------------------------------------------- + 37 | 281474976710658 + 38 | 281474976710659 +(2 rows) + +\c - mx_user - :worker_2_port +SELECT nextval('mx_table_b_seq'); + nextval +--------------------------------------------------------------------- + 1125899906842625 +(1 row) + +INSERT INTO mx_table (a) VALUES (39); +INSERT INTO mx_table (a) VALUES (40); +SELECT * FROM mx_table ORDER BY a; + a | b +--------------------------------------------------------------------- + 37 | 281474976710658 + 38 | 281474976710659 + 39 | 1125899906842626 + 40 | 1125899906842627 +(4 rows) + +\c - mx_user - :master_port +DROP TABLE mx_table; +-- put the metadata back into a consistent state +\c - postgres - :master_port +INSERT INTO pg_dist_placement SELECT * FROM pg_dist_placement_temp; +INSERT INTO pg_dist_partition SELECT * FROM pg_dist_partition_temp; +INSERT INTO pg_catalog.pg_dist_object SELECT * FROM pg_dist_object_temp ON CONFLICT ON CONSTRAINT pg_dist_object_pkey DO NOTHING; +DROP TABLE pg_dist_placement_temp; +DROP TABLE pg_dist_partition_temp; +DROP TABLE pg_dist_object_temp; +UPDATE pg_dist_placement + SET groupid = (SELECT groupid FROM pg_dist_node WHERE nodeport = :worker_2_port) + WHERE groupid = :old_worker_2_group; +\c - - - :worker_1_port +UPDATE pg_dist_placement + SET groupid = (SELECT groupid FROM pg_dist_node WHERE nodeport = :worker_2_port) + WHERE groupid = :old_worker_2_group; +\c - - - :worker_2_port +UPDATE pg_dist_placement + SET groupid = (SELECT groupid FROM pg_dist_node WHERE nodeport = :worker_2_port) + WHERE groupid = :old_worker_2_group; +\c - - - :master_port +SELECT stop_metadata_sync_to_node('localhost', :worker_2_port); +NOTICE: dropping metadata on the node (localhost,57638) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +DROP USER mx_user; +-- Check that create_reference_table creates the metadata on workers +\c - - - :master_port +CREATE TABLE mx_ref (col_1 int, col_2 text); +SELECT create_reference_table('mx_ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +-- make sure that adding/removing nodes doesn't cause +-- multiple colocation entries for reference tables +SELECT count(*) FROM pg_dist_colocation WHERE distributioncolumntype = 0; + count +--------------------------------------------------------------------- + 1 +(1 row) + +\dt mx_ref + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_ref | table | postgres +(1 row) + +\c - - - :worker_1_port +\dt mx_ref + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_ref | table | postgres +(1 row) + +SELECT + logicalrelid, partmethod, repmodel, shardid, placementid, nodename, nodeport +FROM + pg_dist_partition + NATURAL JOIN pg_dist_shard + NATURAL JOIN pg_dist_shard_placement +WHERE + logicalrelid = 'mx_ref'::regclass +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) + +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 +SELECT count(*) FROM pg_dist_colocation WHERE distributioncolumntype = 0; + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- Check that DDL commands are propagated to reference tables on workers +\c - - - :master_port +ALTER TABLE mx_ref ADD COLUMN col_3 NUMERIC DEFAULT 0; +CREATE INDEX mx_ref_index ON mx_ref(col_1); +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_ref'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + col_1 | integer | + col_2 | text | + col_3 | numeric | default 0 +(3 rows) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_ref_index'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col_1 | integer | col_1 +(1 row) + +\c - - - :worker_1_port +SELECT "Column", "Type", "Modifiers" FROM table_desc WHERE relid='mx_ref'::regclass; + Column | Type | Modifiers +--------------------------------------------------------------------- + col_1 | integer | + col_2 | text | + col_3 | numeric | default 0 +(3 rows) + +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_ref_index'::regclass; + Column | Type | Definition +--------------------------------------------------------------------- + col_1 | integer | col_1 +(1 row) + +-- Check that metada is cleaned successfully upon drop table +\c - - - :master_port +DROP TABLE mx_ref; +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_ref_index'::regclass; +ERROR: relation "mx_ref_index" does not exist +\c - - - :worker_1_port +SELECT "Column", "Type", "Definition" FROM index_attrs WHERE + relid = 'mx_ref_index'::regclass; +ERROR: relation "mx_ref_index" does not exist +SELECT * FROM pg_dist_shard WHERE shardid=:ref_table_shardid; + logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue +--------------------------------------------------------------------- +(0 rows) + +SELECT * FROM pg_dist_shard_placement WHERE shardid=:ref_table_shardid; + shardid | shardstate | shardlength | nodename | nodeport | placementid +--------------------------------------------------------------------- +(0 rows) + +-- Check that master_add_node propagates the metadata about new placements of a reference table +\c - - - :master_port +SELECT groupid AS old_worker_2_group + FROM pg_dist_node WHERE nodeport = :worker_2_port \gset +CREATE TABLE tmp_placement AS + SELECT * FROM pg_dist_placement WHERE groupid = :old_worker_2_group; +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 + master_remove_node +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE mx_ref (col_1 int, col_2 text); +SELECT create_reference_table('mx_ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT shardid, nodename, nodeport +FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement +WHERE logicalrelid='mx_ref'::regclass; + shardid | nodename | nodeport +--------------------------------------------------------------------- + 1310073 | localhost | 57637 +(1 row) + +\c - - - :worker_1_port +SELECT shardid, nodename, nodeport +FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement +WHERE logicalrelid='mx_ref'::regclass; + shardid | nodename | nodeport +--------------------------------------------------------------------- + 1310073 | localhost | 57637 +(1 row) + +\c - - - :master_port +SET client_min_messages TO ERROR; +SELECT master_add_node('localhost', :worker_2_port); + master_add_node +--------------------------------------------------------------------- + 7 +(1 row) + +RESET client_min_messages; +SELECT shardid, nodename, nodeport +FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement +WHERE logicalrelid='mx_ref'::regclass +ORDER BY shardid, nodeport; + shardid | nodename | nodeport +--------------------------------------------------------------------- + 1310073 | localhost | 57637 + 1310073 | localhost | 57638 +(2 rows) + +\c - - - :worker_1_port +SELECT shardid, nodename, nodeport +FROM pg_dist_shard NATURAL JOIN pg_dist_shard_placement +WHERE logicalrelid='mx_ref'::regclass +ORDER BY shardid, nodeport; + shardid | nodename | nodeport +--------------------------------------------------------------------- + 1310073 | localhost | 57637 + 1310073 | localhost | 57638 +(2 rows) + +-- Get the metadata back into a consistent state +\c - - - :master_port +INSERT INTO pg_dist_placement (SELECT * FROM tmp_placement); +DROP TABLE tmp_placement; +UPDATE pg_dist_placement + SET groupid = (SELECT groupid FROM pg_dist_node WHERE nodeport = :worker_2_port) + WHERE groupid = :old_worker_2_group; +\c - - - :worker_1_port +UPDATE pg_dist_placement + SET groupid = (SELECT groupid FROM pg_dist_node WHERE nodeport = :worker_2_port) + WHERE groupid = :old_worker_2_group; +-- Confirm that shouldhaveshards is 'true' +\c - - - :master_port +select shouldhaveshards from pg_dist_node where nodeport = 8888; + shouldhaveshards +--------------------------------------------------------------------- + t +(1 row) + +\c - postgres - :worker_1_port +select shouldhaveshards from pg_dist_node where nodeport = 8888; + shouldhaveshards +--------------------------------------------------------------------- + t +(1 row) + +-- Check that setting shouldhaveshards to false is correctly transferred to other mx nodes +\c - - - :master_port +SELECT * from master_set_node_property('localhost', 8888, 'shouldhaveshards', false); + master_set_node_property +--------------------------------------------------------------------- + +(1 row) + +select shouldhaveshards from pg_dist_node where nodeport = 8888; + shouldhaveshards +--------------------------------------------------------------------- + f +(1 row) + +\c - postgres - :worker_1_port +select shouldhaveshards from pg_dist_node where nodeport = 8888; + shouldhaveshards +--------------------------------------------------------------------- + f +(1 row) + +-- Check that setting shouldhaveshards to true is correctly transferred to other mx nodes +\c - postgres - :master_port +SELECT * from master_set_node_property('localhost', 8888, 'shouldhaveshards', true); + master_set_node_property +--------------------------------------------------------------------- + +(1 row) + +select shouldhaveshards from pg_dist_node where nodeport = 8888; + shouldhaveshards +--------------------------------------------------------------------- + t +(1 row) + +\c - postgres - :worker_1_port +select shouldhaveshards from pg_dist_node where nodeport = 8888; + shouldhaveshards +--------------------------------------------------------------------- + t +(1 row) + +\c - - - :master_port +-- +-- Check that metadata commands error out if any nodes are out-of-sync +-- +-- increase metadata_sync intervals to avoid metadata sync while we test +ALTER SYSTEM SET citus.metadata_sync_interval TO 300000; +ALTER SYSTEM SET citus.metadata_sync_retry_interval TO 300000; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SET citus.shard_replication_factor TO 1; +CREATE TABLE dist_table_1(a int); +SELECT create_distributed_table('dist_table_1', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +UPDATE pg_dist_node SET metadatasynced=false WHERE nodeport=:worker_1_port; +SELECT hasmetadata, metadatasynced FROM pg_dist_node WHERE nodeport=:worker_1_port; + hasmetadata | metadatasynced +--------------------------------------------------------------------- + t | f +(1 row) + +CREATE TABLE dist_table_2(a int); +SELECT create_distributed_table('dist_table_2', 'a'); +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 create_reference_table('dist_table_2'); +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. +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); +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. +HINT: You can force disabling node, SELECT citus_disable_node('localhost', 57637, synchronous:=true); +CONTEXT: SQL statement "SELECT pg_catalog.citus_disable_node(nodename, nodeport, force)" +PL/pgSQL function citus_disable_node_and_wait(text,integer,boolean) line XX at PERFORM +SELECT citus_disable_node_and_wait('localhost', :worker_2_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 mx_testing_schema.mx_test_table +HINT: To proceed, either drop the tables or use undistribute_table() function to convert them to local tables +CONTEXT: SQL statement "SELECT pg_catalog.citus_disable_node(nodename, nodeport, force)" +PL/pgSQL function citus_disable_node_and_wait(text,integer,boolean) line XX at PERFORM +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 mx_testing_schema.mx_test_table +HINT: To proceed, either drop the tables or use undistribute_table() function to convert them to local tables +SELECT master_remove_node('localhost', :worker_2_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 mx_testing_schema.mx_test_table +HINT: To proceed, either drop the tables or use undistribute_table() function to convert them to local tables +-- master_update_node should succeed +SELECT nodeid AS worker_2_nodeid FROM pg_dist_node WHERE nodeport=:worker_2_port \gset +SELECT master_update_node(:worker_2_nodeid, 'localhost', 4444); + master_update_node +--------------------------------------------------------------------- + +(1 row) + +SELECT master_update_node(:worker_2_nodeid, 'localhost', :worker_2_port); + master_update_node +--------------------------------------------------------------------- + +(1 row) + +ALTER SYSTEM SET citus.metadata_sync_interval TO DEFAULT; +ALTER SYSTEM SET citus.metadata_sync_retry_interval TO DEFAULT; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +-- make sure that all the nodes have valid metadata before moving forward +SELECT wait_until_metadata_sync(60000); + wait_until_metadata_sync +--------------------------------------------------------------------- + +(1 row) + +SELECT master_add_node('localhost', :worker_2_port); + master_add_node +--------------------------------------------------------------------- + 7 +(1 row) + +CREATE SEQUENCE mx_test_sequence_0; +CREATE SEQUENCE mx_test_sequence_1; +-- test create_distributed_table +CREATE TABLE test_table (id int DEFAULT nextval('mx_test_sequence_0')); +SELECT create_distributed_table('test_table', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- shouldn't work since it's partition column +ALTER TABLE test_table ALTER COLUMN id SET DEFAULT nextval('mx_test_sequence_1'); +ERROR: cannot execute ALTER TABLE command involving partition column +-- test different plausible commands +ALTER TABLE test_table ADD COLUMN id2 int DEFAULT nextval('mx_test_sequence_1'); +ALTER TABLE test_table ALTER COLUMN id2 DROP DEFAULT; +ALTER TABLE test_table ALTER COLUMN id2 SET DEFAULT nextval('mx_test_sequence_1'); +SELECT unnest(activate_node_snapshot()) order by 1; + unnest +--------------------------------------------------------------------- + ALTER DATABASE regression OWNER TO postgres; + ALTER SEQUENCE mx_testing_schema.mx_test_table_col_3_seq OWNER TO postgres + ALTER SEQUENCE public.mx_test_sequence_0 OWNER TO postgres + ALTER SEQUENCE public.mx_test_sequence_1 OWNER TO postgres + ALTER SEQUENCE public.user_defined_seq OWNER TO postgres + ALTER TABLE mx_test_schema_1.mx_table_1 ADD CONSTRAINT mx_fk_constraint_2 FOREIGN KEY (col1) REFERENCES mx_test_schema_2.mx_table_2(col1) NOT VALID + ALTER TABLE mx_test_schema_1.mx_table_1 ADD CONSTRAINT mx_table_1_col1_key UNIQUE (col1) + ALTER TABLE mx_test_schema_1.mx_table_1 OWNER TO postgres + ALTER TABLE mx_test_schema_2.mx_table_2 ADD CONSTRAINT mx_fk_constraint FOREIGN KEY (col1) REFERENCES mx_test_schema_1.mx_table_1(col1) + ALTER TABLE mx_test_schema_2.mx_table_2 ADD CONSTRAINT mx_table_2_col1_key UNIQUE (col1) + ALTER TABLE mx_test_schema_2.mx_table_2 OWNER TO postgres + ALTER TABLE mx_testing_schema.mx_test_table ADD CONSTRAINT mx_test_table_col_1_key UNIQUE (col_1) + ALTER TABLE mx_testing_schema.mx_test_table OWNER TO postgres + ALTER TABLE public.dist_table_1 OWNER TO postgres + ALTER TABLE public.mx_ref OWNER TO postgres + ALTER TABLE public.test_table OWNER TO postgres + CREATE INDEX mx_index ON mx_testing_schema.mx_test_table USING btree (col_2) + CREATE INDEX mx_index_1 ON mx_test_schema_1.mx_table_1 USING btree (col1) + CREATE INDEX mx_index_2 ON mx_test_schema_2.mx_table_2 USING btree (col2) + CREATE SCHEMA IF NOT EXISTS mx_test_schema_1 AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS mx_test_schema_2 AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS mx_testing_schema AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS mx_testing_schema_2 AUTHORIZATION postgres + CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION postgres + CREATE TABLE mx_test_schema_1.mx_table_1 (col1 integer, col2 text, col3 integer) + CREATE TABLE mx_test_schema_2.mx_table_2 (col1 integer, col2 text) + 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)) + CREATE TABLE public.dist_table_1 (a integer) + CREATE TABLE public.mx_ref (col_1 integer, col_2 text) + 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)) + DELETE FROM pg_catalog.pg_dist_colocation + DELETE FROM pg_catalog.pg_dist_object + DELETE FROM pg_dist_node + DELETE FROM pg_dist_partition + DELETE FROM pg_dist_placement + DELETE FROM pg_dist_shard + 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 (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) + 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_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(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') + SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.mx_test_sequence_0 AS integer INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1 NO CYCLE','integer') + SELECT worker_apply_sequence_command ('CREATE SEQUENCE IF NOT EXISTS public.mx_test_sequence_1 AS integer INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1 NO CYCLE','integer') + 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('mx_test_schema_1.mx_table_1') + SELECT worker_create_truncate_trigger('mx_test_schema_2.mx_table_2') + SELECT worker_create_truncate_trigger('mx_testing_schema.mx_test_table') + SELECT worker_create_truncate_trigger('public.dist_table_1') + SELECT worker_create_truncate_trigger('public.mx_ref') + SELECT worker_create_truncate_trigger('public.test_table') + SELECT worker_drop_shell_table(logicalrelid::regclass::text) FROM pg_dist_partition + SET ROLE postgres + SET ROLE postgres + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'off' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + SET citus.enable_ddl_propagation TO 'on' + UPDATE pg_dist_local_group SET groupid = 1 + WITH colocation_group_data (colocationid, shardcount, replicationfactor, distributioncolumntype, distributioncolumncollationname, distributioncolumncollationschema) AS (VALUES (10009, 1, -1, 0, NULL, NULL), (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 ('sequence', ARRAY['public', 'user_defined_seq']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['mx_testing_schema', 'mx_test_table_col_3_seq']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_testing_schema', 'mx_test_table']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_test_schema_1', 'mx_table_1']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['mx_test_schema_2', 'mx_table_2']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'mx_ref']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'dist_table_1']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_sequence_0']::text[], ARRAY[]::text[], -1, 0, false), ('sequence', ARRAY['public', 'mx_test_sequence_1']::text[], ARRAY[]::text[], -1, 0, false), ('table', ARRAY['public', 'test_table']::text[], ARRAY[]::text[], -1, 0, false), ('role', ARRAY['postgres']::text[], ARRAY[]::text[], -1, 0, false), ('database', ARRAY['regression']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['public']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_testing_schema_2']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_test_schema_1']::text[], ARRAY[]::text[], -1, 0, false), ('schema', ARRAY['mx_test_schema_2']::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, shardstate, shardlength, groupid, placementid) AS (VALUES (1310000, 1, 0, 1, 100000), (1310001, 1, 0, 5, 100001), (1310002, 1, 0, 1, 100002), (1310003, 1, 0, 5, 100003), (1310004, 1, 0, 1, 100004), (1310005, 1, 0, 5, 100005), (1310006, 1, 0, 1, 100006), (1310007, 1, 0, 5, 100007)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310020, 1, 0, 1, 100020), (1310021, 1, 0, 5, 100021), (1310022, 1, 0, 1, 100022), (1310023, 1, 0, 5, 100023), (1310024, 1, 0, 1, 100024)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310025, 1, 0, 1, 100025), (1310026, 1, 0, 5, 100026), (1310027, 1, 0, 1, 100027), (1310028, 1, 0, 5, 100028), (1310029, 1, 0, 1, 100029)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310073, 1, 0, 1, 100074), (1310073, 1, 0, 5, 100075)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310074, 1, 0, 1, 100076), (1310075, 1, 0, 5, 100077), (1310076, 1, 0, 1, 100078), (1310077, 1, 0, 5, 100079)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, shardlength, groupid, placementid) FROM placement_data; + WITH placement_data(shardid, shardstate, shardlength, groupid, placementid) AS (VALUES (1310083, 1, 0, 1, 100086), (1310084, 1, 0, 5, 100087), (1310085, 1, 0, 1, 100088), (1310086, 1, 0, 5, 100089)) SELECT citus_internal_add_placement_metadata(shardid, shardstate, 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 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; +(87 rows) + +-- shouldn't work since test_table is MX +ALTER TABLE test_table ADD COLUMN id3 bigserial; +ERROR: cannot execute ADD COLUMN commands involving serial pseudotypes when metadata is synchronized to workers +-- shouldn't work since the above operations should be the only subcommands +ALTER TABLE test_table ADD COLUMN id4 int DEFAULT nextval('mx_test_sequence_1') CHECK (id4 > 0); +ERROR: cannot execute ADD COLUMN .. DEFAULT nextval('..') command with other subcommands/constraints +HINT: You can issue each subcommand separately +ALTER TABLE test_table ADD COLUMN id4 int, ADD COLUMN id5 int DEFAULT nextval('mx_test_sequence_1'); +ERROR: cannot execute ADD COLUMN .. DEFAULT nextval('..') command with other subcommands/constraints +HINT: You can issue each subcommand separately +ALTER TABLE test_table ALTER COLUMN id1 SET DEFAULT nextval('mx_test_sequence_1'), ALTER COLUMN id2 DROP DEFAULT; +ERROR: cannot execute ALTER COLUMN COLUMN .. SET DEFAULT nextval('..') command with other subcommands +HINT: You can issue each subcommand separately +ALTER TABLE test_table ADD COLUMN id4 bigserial CHECK (id4 > 0); +ERROR: cannot execute ADD COLUMN commands involving serial pseudotypes when metadata is synchronized to workers +\c - - - :worker_1_port +\ds + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_test_sequence_0 | sequence | postgres + public | mx_test_sequence_1 | sequence | postgres + public | mx_test_table_col_3_seq | sequence | postgres + public | sequence_rollback | sequence | postgres + public | sequence_rollback(citus_backup_0) | sequence | postgres + public | user_defined_seq | sequence | postgres +(6 rows) + +\c - - - :master_port +CREATE SEQUENCE local_sequence; +-- verify that DROP SEQUENCE will propagate the command to workers for +-- the distributed sequences mx_test_sequence_0 and mx_test_sequence_1 +DROP SEQUENCE mx_test_sequence_0, mx_test_sequence_1, local_sequence CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to default value for column id2 of table test_table +drop cascades to default value for column id of table test_table +\c - - - :worker_1_port +\ds + List of relations + Schema | Name | Type | Owner +--------------------------------------------------------------------- + public | mx_test_table_col_3_seq | sequence | postgres + public | sequence_rollback | sequence | postgres + public | sequence_rollback(citus_backup_0) | sequence | postgres + public | user_defined_seq | sequence | postgres +(4 rows) + +\c - - - :master_port +DROP TABLE test_table CASCADE; +-- Cleanup +SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); +NOTICE: dropping metadata on the node (localhost,57637) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT stop_metadata_sync_to_node('localhost', :worker_2_port); +NOTICE: dropping metadata on the node (localhost,57638) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +DROP TABLE mx_test_schema_2.mx_table_2 CASCADE; +NOTICE: drop cascades to constraint mx_fk_constraint_2 on table mx_test_schema_1.mx_table_1 +DROP TABLE mx_test_schema_1.mx_table_1 CASCADE; +DROP TABLE mx_testing_schema.mx_test_table; +DROP TABLE mx_ref; +DROP TABLE dist_table_1, dist_table_2; +SET client_min_messages TO ERROR; +SET citus.enable_ddl_propagation TO off; -- for enterprise +CREATE USER non_super_metadata_user; +SET citus.enable_ddl_propagation TO on; +RESET client_min_messages; +SELECT run_command_on_workers('CREATE USER non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,"CREATE ROLE") + (localhost,57638,t,"CREATE ROLE") +(2 rows) + +GRANT EXECUTE ON FUNCTION start_metadata_sync_to_node(text,int) TO non_super_metadata_user; +GRANT EXECUTE ON FUNCTION stop_metadata_sync_to_node(text,int,bool) TO non_super_metadata_user; +GRANT ALL ON pg_dist_node TO non_super_metadata_user; +GRANT ALL ON pg_dist_local_group TO non_super_metadata_user; +GRANT ALL ON SCHEMA citus TO non_super_metadata_user; +GRANT INSERT ON ALL TABLES IN SCHEMA citus TO non_super_metadata_user; +GRANT USAGE ON SCHEMA mx_testing_schema TO non_super_metadata_user; +GRANT USAGE ON SCHEMA mx_testing_schema_2 TO non_super_metadata_user; +GRANT USAGE ON SCHEMA mx_test_schema_1 TO non_super_metadata_user; +GRANT USAGE ON SCHEMA mx_test_schema_2 TO non_super_metadata_user; +SELECT run_command_on_workers('GRANT ALL ON pg_dist_node TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SELECT run_command_on_workers('GRANT ALL ON pg_dist_local_group TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SELECT run_command_on_workers('GRANT ALL ON SCHEMA citus TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SELECT run_command_on_workers('ALTER SEQUENCE user_defined_seq OWNER TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,"ALTER SEQUENCE") + (localhost,57638,t,"ALTER SEQUENCE") +(2 rows) + +SELECT run_command_on_workers('GRANT ALL ON ALL TABLES IN SCHEMA citus TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SELECT run_command_on_workers('GRANT USAGE ON SCHEMA mx_testing_schema TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SELECT run_command_on_workers('GRANT USAGE ON SCHEMA mx_testing_schema_2 TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SELECT run_command_on_workers('GRANT USAGE ON SCHEMA mx_test_schema_1 TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SELECT run_command_on_workers('GRANT USAGE ON SCHEMA mx_test_schema_2 TO non_super_metadata_user'); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,GRANT) + (localhost,57638,t,GRANT) +(2 rows) + +SET ROLE non_super_metadata_user; +-- user must be super user stop/start metadata +SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); +ERROR: operation is not allowed +HINT: Run the command with a superuser. +SELECT start_metadata_sync_to_node('localhost', :worker_1_port); +ERROR: operation is not allowed +HINT: Run the command with a superuser. +RESET ROLE; +SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); +NOTICE: dropping metadata on the node (localhost,57637) + stop_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +SELECT start_metadata_sync_to_node('localhost', :worker_1_port); + start_metadata_sync_to_node +--------------------------------------------------------------------- + +(1 row) + +RESET citus.shard_count; +RESET citus.shard_replication_factor; +ALTER SEQUENCE pg_catalog.pg_dist_groupid_seq RESTART :last_group_id; +ALTER SEQUENCE pg_catalog.pg_dist_node_nodeid_seq RESTART :last_node_id; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART :last_colocation_id; +ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART :last_placement_id; +-- Activate them at the end +SELECT 1 FROM citus_activate_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT 1 FROM citus_activate_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + diff --git a/src/test/regress/expected/multi_multiuser_master_protocol.out b/src/test/regress/expected/multi_multiuser_master_protocol.out index e547363ae..071f12b9b 100644 --- a/src/test/regress/expected/multi_multiuser_master_protocol.out +++ b/src/test/regress/expected/multi_multiuser_master_protocol.out @@ -449,10 +449,12 @@ ORDER BY nodename, nodeport, shardid; nodename | nodeport | shardid | success | result --------------------------------------------------------------------- localhost | 57637 | 109102 | t | t + localhost | 57637 | 109103 | t | t localhost | 57637 | 109104 | t | t - localhost | 57638 | 109103 | t | t + localhost | 57637 | 109106 | t | t localhost | 57638 | 109105 | t | t -(4 rows) + localhost | 57638 | 109107 | t | t +(6 rows) -- revoke from multiple schemas, verify result REVOKE SELECT ON ALL TABLES IN SCHEMA multiuser_schema, multiuser_second_schema FROM read_access; @@ -473,10 +475,12 @@ ORDER BY nodename, nodeport, shardid; nodename | nodeport | shardid | success | result --------------------------------------------------------------------- localhost | 57637 | 109102 | t | f + localhost | 57637 | 109103 | t | f localhost | 57637 | 109104 | t | f - localhost | 57638 | 109103 | t | f + localhost | 57637 | 109106 | t | f localhost | 57638 | 109105 | t | f -(4 rows) + localhost | 57638 | 109107 | t | f +(6 rows) DROP SCHEMA multiuser_schema CASCADE; NOTICE: drop cascades to 3 other objects diff --git a/src/test/regress/expected/multi_mx_alter_distributed_table.out b/src/test/regress/expected/multi_mx_alter_distributed_table.out index 427dfe027..9557cac3a 100644 --- a/src/test/regress/expected/multi_mx_alter_distributed_table.out +++ b/src/test/regress/expected/multi_mx_alter_distributed_table.out @@ -269,13 +269,13 @@ RESET client_min_messages; SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::regclass::text IN ('test_proc_colocation_0'); logicalrelid | colocationid --------------------------------------------------------------------- - test_proc_colocation_0 | 1410004 + test_proc_colocation_0 | 1410005 (1 row) SELECT proname, colocationid FROM pg_proc JOIN pg_catalog.pg_dist_object ON pg_proc.oid = pg_catalog.pg_dist_object.objid WHERE proname IN ('proc_0'); proname | colocationid --------------------------------------------------------------------- - proc_0 | 1410004 + proc_0 | 1410005 (1 row) -- shardCount is not null && cascade_to_colocated is true @@ -302,13 +302,13 @@ RESET client_min_messages; SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::regclass::text IN ('test_proc_colocation_0'); logicalrelid | colocationid --------------------------------------------------------------------- - test_proc_colocation_0 | 1410003 + test_proc_colocation_0 | 1410006 (1 row) SELECT proname, colocationid FROM pg_proc JOIN pg_catalog.pg_dist_object ON pg_proc.oid = pg_catalog.pg_dist_object.objid WHERE proname IN ('proc_0'); proname | colocationid --------------------------------------------------------------------- - proc_0 | 1410003 + proc_0 | 1410006 (1 row) -- colocatewith is not null && cascade_to_colocated is true @@ -356,13 +356,13 @@ RESET client_min_messages; SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::regclass::text IN ('test_proc_colocation_0'); logicalrelid | colocationid --------------------------------------------------------------------- - test_proc_colocation_0 | 1410005 + test_proc_colocation_0 | 1410008 (1 row) SELECT proname, colocationid FROM pg_proc JOIN pg_catalog.pg_dist_object ON pg_proc.oid = pg_catalog.pg_dist_object.objid WHERE proname IN ('proc_0'); proname | colocationid --------------------------------------------------------------------- - proc_0 | 1410005 + proc_0 | 1410008 (1 row) -- try a case with more than one procedure @@ -386,14 +386,14 @@ SELECT create_distributed_function('proc_1(float8)', 'dist_key', 'test_proc_colo SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::regclass::text IN ('test_proc_colocation_0'); logicalrelid | colocationid --------------------------------------------------------------------- - test_proc_colocation_0 | 1410005 + test_proc_colocation_0 | 1410008 (1 row) SELECT proname, colocationid FROM pg_proc JOIN pg_catalog.pg_dist_object ON pg_proc.oid = pg_catalog.pg_dist_object.objid WHERE proname IN ('proc_0', 'proc_1') ORDER BY proname; proname | colocationid --------------------------------------------------------------------- - proc_0 | 1410005 - proc_1 | 1410005 + proc_0 | 1410008 + proc_1 | 1410008 (2 rows) SET client_min_messages TO DEBUG1; @@ -437,14 +437,14 @@ RESET client_min_messages; SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::regclass::text IN ('test_proc_colocation_0'); logicalrelid | colocationid --------------------------------------------------------------------- - test_proc_colocation_0 | 1410003 + test_proc_colocation_0 | 1410009 (1 row) SELECT proname, colocationid FROM pg_proc JOIN pg_catalog.pg_dist_object ON pg_proc.oid = pg_catalog.pg_dist_object.objid WHERE proname IN ('proc_0', 'proc_1') ORDER BY proname; proname | colocationid --------------------------------------------------------------------- - proc_0 | 1410003 - proc_1 | 1410003 + proc_0 | 1410009 + proc_1 | 1410009 (2 rows) -- case which shouldn't preserve colocation for now @@ -462,14 +462,14 @@ NOTICE: renaming the new table to mx_alter_distributed_table.test_proc_colocati SELECT logicalrelid, colocationid FROM pg_dist_partition WHERE logicalrelid::regclass::text IN ('test_proc_colocation_0'); logicalrelid | colocationid --------------------------------------------------------------------- - test_proc_colocation_0 | 1410006 + test_proc_colocation_0 | 1410010 (1 row) SELECT proname, colocationid FROM pg_proc JOIN pg_catalog.pg_dist_object ON pg_proc.oid = pg_catalog.pg_dist_object.objid WHERE proname IN ('proc_0', 'proc_1') ORDER BY proname; proname | colocationid --------------------------------------------------------------------- - proc_0 | 1410003 - proc_1 | 1410003 + proc_0 | 1410009 + proc_1 | 1410009 (2 rows) SET client_min_messages TO WARNING; diff --git a/src/test/regress/expected/multi_mx_create_table.out b/src/test/regress/expected/multi_mx_create_table.out index 6036bd325..ffbaa738e 100644 --- a/src/test/regress/expected/multi_mx_create_table.out +++ b/src/test/regress/expected/multi_mx_create_table.out @@ -2,6 +2,7 @@ -- MULTI_MX_CREATE_TABLE -- ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 1220000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 1220000; SELECT start_metadata_sync_to_node('localhost', :worker_1_port); start_metadata_sync_to_node --------------------------------------------------------------------- @@ -406,29 +407,29 @@ FROM pg_dist_partition NATURAL JOIN shard_counts ORDER BY colocationid, logicalrelid; logicalrelid | colocationid | shard_count | partmethod | repmodel --------------------------------------------------------------------- - customer_mx | 1390001 | 1 | n | t - nation_mx | 1390001 | 1 | n | t - part_mx | 1390001 | 1 | n | t - supplier_mx | 1390001 | 1 | n | t - citus_mx_test_schema_join_1.nation_hash | 1390003 | 4 | h | s - citus_mx_test_schema_join_1.nation_hash_2 | 1390003 | 4 | h | s - citus_mx_test_schema_join_2.nation_hash | 1390003 | 4 | h | s - citus_mx_test_schema.nation_hash_collation_search_path | 1390003 | 4 | h | s - citus_mx_test_schema.nation_hash_composite_types | 1390003 | 4 | h | s - mx_ddl_table | 1390003 | 4 | h | s - app_analytics_events_mx | 1390003 | 4 | h | s - company_employees_mx | 1390003 | 4 | h | s - nation_hash | 1390006 | 16 | h | s - citus_mx_test_schema.nation_hash | 1390006 | 16 | h | s - lineitem_mx | 1390007 | 16 | h | s - orders_mx | 1390007 | 16 | h | s - limit_orders_mx | 1390008 | 2 | h | s - articles_hash_mx | 1390008 | 2 | h | s - multiple_hash_mx | 1390009 | 2 | h | s - researchers_mx | 1390010 | 2 | h | s - labs_mx | 1390011 | 1 | h | s - objects_mx | 1390011 | 1 | h | s - articles_single_shard_hash_mx | 1390011 | 1 | h | s + nation_hash | 1220000 | 16 | h | s + citus_mx_test_schema.nation_hash | 1220000 | 16 | h | s + citus_mx_test_schema_join_1.nation_hash | 1220001 | 4 | h | s + citus_mx_test_schema_join_1.nation_hash_2 | 1220001 | 4 | h | s + citus_mx_test_schema_join_2.nation_hash | 1220001 | 4 | h | s + citus_mx_test_schema.nation_hash_collation_search_path | 1220001 | 4 | h | s + citus_mx_test_schema.nation_hash_composite_types | 1220001 | 4 | h | s + mx_ddl_table | 1220001 | 4 | h | s + app_analytics_events_mx | 1220001 | 4 | h | s + company_employees_mx | 1220001 | 4 | h | s + lineitem_mx | 1220002 | 16 | h | s + orders_mx | 1220002 | 16 | h | s + customer_mx | 1220003 | 1 | n | t + nation_mx | 1220003 | 1 | n | t + part_mx | 1220003 | 1 | n | t + supplier_mx | 1220003 | 1 | n | t + limit_orders_mx | 1220004 | 2 | h | s + articles_hash_mx | 1220004 | 2 | h | s + multiple_hash_mx | 1220005 | 2 | h | s + researchers_mx | 1220006 | 2 | h | s + labs_mx | 1220007 | 1 | h | s + objects_mx | 1220007 | 1 | h | s + articles_single_shard_hash_mx | 1220007 | 1 | h | s (23 rows) -- check the citus_tables view diff --git a/src/test/regress/expected/multi_mx_insert_select_repartition.out b/src/test/regress/expected/multi_mx_insert_select_repartition.out index 47fd8d18f..62f197c30 100644 --- a/src/test/regress/expected/multi_mx_insert_select_repartition.out +++ b/src/test/regress/expected/multi_mx_insert_select_repartition.out @@ -1,4 +1,19 @@ +-- +-- MULTI_MX_INSERT_SELECT_REPARTITION +-- -- Test behaviour of repartitioned INSERT ... SELECT in MX setup +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA multi_mx_insert_select_repartition; SET search_path TO multi_mx_insert_select_repartition; SET citus.next_shard_id TO 4213581; @@ -92,8 +107,8 @@ NOTICE: executing the command locally: SELECT count(*) AS count FROM multi_mx_i NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_4213581_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_4213581_to','SELECT (a OPERATOR(pg_catalog.*) 2) AS a FROM multi_mx_insert_select_repartition.source_table_4213581 source_table WHERE true',0,'hash','{-2147483648,-715827883,715827882}'::text[],'{-715827884,715827881,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_4213583_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_4213583_to','SELECT (a OPERATOR(pg_catalog.*) 2) AS a FROM multi_mx_insert_select_repartition.source_table_4213583 source_table WHERE true',0,'hash','{-2147483648,-715827883,715827882}'::text[],'{-715827884,715827881,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartitioned_results_xxxxx_from_4213582_to_0','repartitioned_results_xxxxx_from_4213584_to_0']::text[],'localhost',57638) bytes -NOTICE: executing the command locally: INSERT INTO multi_mx_insert_select_repartition.target_table_4213585 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_0,repartitioned_results_xxxxx_from_4213582_to_0,repartitioned_results_xxxxx_from_4213584_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) RETURNING citus_table_alias.a -NOTICE: executing the command locally: INSERT INTO multi_mx_insert_select_repartition.target_table_4213587 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) RETURNING citus_table_alias.a +NOTICE: executing the command locally: INSERT INTO multi_mx_insert_select_repartition.target_table_4213585 AS citus_table_alias (a) SELECT intermediate_result.a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_0,repartitioned_results_xxxxx_from_4213582_to_0,repartitioned_results_xxxxx_from_4213584_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) RETURNING citus_table_alias.a +NOTICE: executing the command locally: INSERT INTO multi_mx_insert_select_repartition.target_table_4213587 AS citus_table_alias (a) SELECT intermediate_result.a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) RETURNING citus_table_alias.a a --------------------------------------------------------------------- 0 diff --git a/src/test/regress/expected/multi_mx_insert_select_repartition_0.out b/src/test/regress/expected/multi_mx_insert_select_repartition_0.out new file mode 100644 index 000000000..15deba0c0 --- /dev/null +++ b/src/test/regress/expected/multi_mx_insert_select_repartition_0.out @@ -0,0 +1,166 @@ +-- +-- MULTI_MX_INSERT_SELECT_REPARTITION +-- +-- Test behaviour of repartitioned INSERT ... SELECT in MX setup +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA multi_mx_insert_select_repartition; +SET search_path TO multi_mx_insert_select_repartition; +SET citus.next_shard_id TO 4213581; +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 4; +CREATE TABLE source_table(a int, b int); +SELECT create_distributed_table('source_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO source_table SELECT floor(i/4), i*i FROM generate_series(1, 20) i; +SET citus.shard_count TO 3; +CREATE TABLE target_table(a int, b int); +SELECT create_distributed_table('target_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE FUNCTION square(int) RETURNS INT + AS $$ SELECT $1 * $1 $$ + LANGUAGE SQL; +select create_distributed_function('square(int)'); +NOTICE: procedure multi_mx_insert_select_repartition.square is already distributed +DETAIL: Citus distributes procedures with CREATE [PROCEDURE|FUNCTION|AGGREGATE] commands + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +select public.colocate_proc_with_table('square', 'source_table'::regclass, 0); + colocate_proc_with_table +--------------------------------------------------------------------- + +(1 row) + +-- Test along with function delegation +-- function delegation only happens for "SELECT f()", and we don't use +-- repartitioned INSERT/SELECT when task count is 1, so the following +-- should go via coordinator +EXPLAIN (costs off) INSERT INTO target_table(a) SELECT square(4); + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: pull to coordinator + -> Result +(3 rows) + +INSERT INTO target_table(a) SELECT square(4); +SELECT * FROM target_table; + a | b +--------------------------------------------------------------------- + 16 | +(1 row) + +TRUNCATE target_table; +-- +-- Test repartitioned INSERT/SELECT from MX worker +-- +\c - - - :worker_1_port +SET search_path TO multi_mx_insert_select_repartition; +EXPLAIN (costs off) INSERT INTO target_table SELECT a, max(b) FROM source_table GROUP BY a; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus INSERT ... SELECT) + INSERT/SELECT method: repartition + -> Custom Scan (Citus Adaptive) + Task Count: 4 + Tasks Shown: One of 4 + -> Task + Node: host=localhost port=xxxxx dbname=regression + -> HashAggregate + Group Key: a + -> Seq Scan on source_table_4213581 source_table +(10 rows) + +INSERT INTO target_table SELECT a, max(b) FROM source_table GROUP BY a; +SET citus.log_local_commands to on; +-- INSERT .. SELECT via repartitioning with local execution +BEGIN; + select count(*) from source_table WHERE a = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM multi_mx_insert_select_repartition.source_table_4213581 source_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 4 +(1 row) + + insert into target_table SELECT a*2 FROM source_table RETURNING a; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_4213581_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_4213581_to','SELECT (a OPERATOR(pg_catalog.*) 2) AS a FROM multi_mx_insert_select_repartition.source_table_4213581 source_table WHERE true',0,'hash','{-2147483648,-715827883,715827882}'::text[],'{-715827884,715827881,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_4213583_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_4213583_to','SELECT (a OPERATOR(pg_catalog.*) 2) AS a FROM multi_mx_insert_select_repartition.source_table_4213583 source_table WHERE true',0,'hash','{-2147483648,-715827883,715827882}'::text[],'{-715827884,715827881,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT bytes FROM fetch_intermediate_results(ARRAY['repartitioned_results_xxxxx_from_4213582_to_0','repartitioned_results_xxxxx_from_4213584_to_0']::text[],'localhost',57638) bytes +NOTICE: executing the command locally: INSERT INTO multi_mx_insert_select_repartition.target_table_4213585 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_0,repartitioned_results_xxxxx_from_4213582_to_0,repartitioned_results_xxxxx_from_4213584_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) RETURNING citus_table_alias.a +NOTICE: executing the command locally: INSERT INTO multi_mx_insert_select_repartition.target_table_4213587 AS citus_table_alias (a) SELECT a FROM read_intermediate_results('{repartitioned_results_xxxxx_from_4213581_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer) RETURNING citus_table_alias.a + a +--------------------------------------------------------------------- + 0 + 0 + 0 + 2 + 2 + 2 + 2 + 4 + 4 + 4 + 4 + 6 + 6 + 6 + 6 + 8 + 8 + 8 + 8 + 10 +(20 rows) + +ROLLBACK; +BEGIN; + select count(*) from source_table WHERE a = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM multi_mx_insert_select_repartition.source_table_4213581 source_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 4 +(1 row) + + insert into target_table SELECT a FROM source_table LIMIT 10; +NOTICE: executing the command locally: SELECT a FROM multi_mx_insert_select_repartition.source_table_4213581 source_table WHERE true LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT a FROM multi_mx_insert_select_repartition.source_table_4213583 source_table WHERE true LIMIT '10'::bigint +NOTICE: executing the copy locally for shard xxxxx +ROLLBACK; +\c - - - :master_port +SET search_path TO multi_mx_insert_select_repartition; +SELECT * FROM target_table ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | 9 + 1 | 49 + 2 | 121 + 3 | 225 + 4 | 361 + 5 | 400 +(6 rows) + +RESET client_min_messages; +\set VERBOSITY terse +DROP SCHEMA multi_mx_insert_select_repartition CASCADE; +NOTICE: drop cascades to 3 other objects diff --git a/src/test/regress/expected/multi_poolinfo_usage_0.out b/src/test/regress/expected/multi_poolinfo_usage_0.out deleted file mode 100644 index 2ccf077e4..000000000 --- a/src/test/regress/expected/multi_poolinfo_usage_0.out +++ /dev/null @@ -1,60 +0,0 @@ --- --- MULTI_POOLINFO_USAGE --- --- Test pooler info logic --- --- Test of ability to override host/port for a node -SET citus.shard_replication_factor TO 1; -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 -(1 row) - -\gset -SELECT nodeid AS worker_2_id FROM pg_dist_node WHERE nodename = 'localhost' AND nodeport = :worker_2_port; - worker_2_id ---------------------------------------------------------------------- - 18 -(1 row) - -\gset -CREATE TABLE lotsa_connections (id integer, name text); -SELECT create_distributed_table('lotsa_connections', 'id'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - -INSERT INTO lotsa_connections VALUES (1, 'user'), (2, 'user'), (3, 'user'), (4, 'user'); -SELECT COUNT(*) FROM lotsa_connections; - count ---------------------------------------------------------------------- - 4 -(1 row) - --- put outright bad values -\set VERBOSITY terse -INSERT INTO pg_dist_poolinfo VALUES (:worker_1_id, 'host=failhost'), - (:worker_2_id, 'port=9999'); -\c -SELECT COUNT(*) FROM lotsa_connections; -ERROR: epoll_ctl() failed: No such file or directory --- "re-route" worker one to node two and vice-versa -DELETE FROM pg_dist_poolinfo; -INSERT INTO pg_dist_poolinfo VALUES (:worker_1_id, 'port=' || :worker_2_port), - (:worker_2_id, 'port=' || :worker_1_port); -\c --- this fails because the shards of one worker won't exist on the other and shards --- are still looked up using the node name, not the effective connection host -INSERT INTO lotsa_connections VALUES (1, 'user'), (2, 'user'), (3, 'user'), (4, 'user'); -ERROR: relation "public.lotsa_connections_20000000" does not exist --- tweak poolinfo to use 127.0.0.1 instead of localhost; should work! -DELETE FROM pg_dist_poolinfo; -INSERT INTO pg_dist_poolinfo VALUES (:worker_1_id, 'host=127.0.0.1 port=' || :worker_1_port), - (:worker_2_id, 'host=127.0.0.1 port=' || :worker_2_port); -\c -DELETE FROM lotsa_connections; -DROP TABLE lotsa_connections; -DELETE FROM pg_dist_poolinfo; diff --git a/src/test/regress/expected/multi_replicate_reference_table.out b/src/test/regress/expected/multi_replicate_reference_table.out index ab61cbd6d..2dd670108 100644 --- a/src/test/regress/expected/multi_replicate_reference_table.out +++ b/src/test/regress/expected/multi_replicate_reference_table.out @@ -773,14 +773,15 @@ SELECT 1 FROM master_remove_node('localhost', :worker_2_port); 1 (1 row) -CREATE TABLE ref_table(a int); +CREATE TABLE ref_table(id bigserial PRIMARY KEY, a int); +CREATE INDEX ON ref_table (a); SELECT create_reference_table('ref_table'); create_reference_table --------------------------------------------------------------------- (1 row) -INSERT INTO ref_table SELECT * FROM generate_series(1, 10); +INSERT INTO ref_table(a) SELECT * FROM generate_series(1, 10); -- verify direct call to citus_copy_shard_placement errors if target node is not yet added SELECT citus_copy_shard_placement( (SELECT shardid FROM pg_dist_shard WHERE logicalrelid='ref_table'::regclass::oid), @@ -876,7 +877,7 @@ SELECT replicate_reference_tables('block_writes'); (1 row) -INSERT INTO ref_table VALUES (11); +INSERT INTO ref_table(a) VALUES (11); SELECT count(*), sum(a) FROM ref_table; count | sum --------------------------------------------------------------------- @@ -1039,6 +1040,25 @@ WHERE nodeport=:worker_1_port; 0 (1 row) +-- test that we can use non-blocking rebalance +SELECT 1 FROM master_remove_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT 1 FROM master_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT rebalance_table_shards(shard_transfer_mode := 'force_logical'); + rebalance_table_shards +--------------------------------------------------------------------- + +(1 row) + -- test that metadata is synced on replicate_reference_tables SELECT 1 FROM master_remove_node('localhost', :worker_2_port); ?column? @@ -1085,7 +1105,7 @@ SELECT create_distributed_table('dist_table', 'a'); INSERT INTO dist_table SELECT i, i * i FROM generate_series(1, 20) i; TRUNCATE ref_table; -INSERT INTO ref_table SELECT 2 * i FROM generate_series(1, 5) i; +INSERT INTO ref_table(a) SELECT 2 * i FROM generate_series(1, 5) i; \c - - - :worker_1_port SET search_path TO replicate_reference_table; SELECT array_agg(dist_table.b ORDER BY ref_table.a) diff --git a/src/test/regress/expected/multi_select_distinct.out b/src/test/regress/expected/multi_select_distinct.out index bfb189095..1112124ae 100644 --- a/src/test/regress/expected/multi_select_distinct.out +++ b/src/test/regress/expected/multi_select_distinct.out @@ -306,12 +306,14 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. We expect to see sort+unique -- instead of aggregate plan node to handle distinct. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT count(*) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Unique -> Sort @@ -380,13 +382,15 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. Similar to the explain of -- the query above. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT l_suppkey, count(*) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1 LIMIT 10; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Limit -> Unique @@ -457,13 +461,15 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. This explain errors out due -- to a bug right now, expectation must be corrected after fixing it. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT l_suppkey, avg(l_partkey) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1,2 LIMIT 10; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Limit -> Unique @@ -533,13 +539,15 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. We expect to see sort+unique to -- handle distinct on. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT ON (l_suppkey) avg(l_partkey) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY l_suppkey,1 LIMIT 10; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Limit -> Unique @@ -608,13 +616,15 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. This explain errors out due -- to a bug right now, expectation must be corrected after fixing it. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT avg(ceil(l_partkey / 2)) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1 LIMIT 10; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Limit -> Unique @@ -683,13 +693,15 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. This explain errors out due -- to a bug right now, expectation must be corrected after fixing it. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT sum(l_suppkey) + count(l_partkey) AS dis FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1 LIMIT 10; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Limit -> Unique @@ -733,13 +745,15 @@ SELECT DISTINCT * -- explain the query to see actual plan. We expect to see only one aggregation -- node since group by columns guarantees the uniqueness. +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT * FROM lineitem_hash_part GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 ORDER BY 1,2 LIMIT 10; - QUERY PLAN +$Q$); + coordinator_plan --------------------------------------------------------------------- Limit -> Sort @@ -748,28 +762,20 @@ EXPLAIN (COSTS FALSE) Group Key: remote_scan.l_orderkey, remote_scan.l_partkey, remote_scan.l_suppkey, remote_scan.l_linenumber, remote_scan.l_quantity, remote_scan.l_extendedprice, remote_scan.l_discount, remote_scan.l_tax, remote_scan.l_returnflag, remote_scan.l_linestatus, remote_scan.l_shipdate, remote_scan.l_commitdate, remote_scan.l_receiptdate, remote_scan.l_shipinstruct, remote_scan.l_shipmode, remote_scan.l_comment -> Custom Scan (Citus Adaptive) Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Unique - -> Group - Group Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Sort - Sort Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(17 rows) +(7 rows) -- check the plan if the hash aggreate is disabled. We expect to see only one -- aggregation node since group by columns guarantees the uniqueness. SET enable_hashagg TO off; +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT * FROM lineitem_hash_part GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 ORDER BY 1,2 LIMIT 10; - QUERY PLAN +$Q$); + coordinator_plan --------------------------------------------------------------------- Limit -> Unique @@ -777,17 +783,7 @@ EXPLAIN (COSTS FALSE) Sort Key: remote_scan.l_orderkey, remote_scan.l_partkey, remote_scan.l_suppkey, remote_scan.l_linenumber, remote_scan.l_quantity, remote_scan.l_extendedprice, remote_scan.l_discount, remote_scan.l_tax, remote_scan.l_returnflag, remote_scan.l_linestatus, remote_scan.l_shipdate, remote_scan.l_commitdate, remote_scan.l_receiptdate, remote_scan.l_shipinstruct, remote_scan.l_shipmode, remote_scan.l_comment -> Custom Scan (Citus Adaptive) Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Unique - -> Group - Group Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Sort - Sort Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) +(6 rows) SET enable_hashagg TO on; -- distinct on count distinct @@ -914,12 +910,14 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT ceil(count(case when l_partkey > 100000 THEN 1 ELSE 0 END) / 2) AS count FROM lineitem_hash_part GROUP BY l_suppkey ORDER BY 1; - QUERY PLAN +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Unique -> Sort @@ -940,13 +938,15 @@ EXPLAIN (COSTS FALSE) SET enable_hashagg TO on; -- explain the query to see actual plan with array_agg aggregation. +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT array_agg(l_linenumber), array_length(array_agg(l_linenumber), 1) FROM lineitem_hash_part GROUP BY l_orderkey ORDER BY 2 LIMIT 15; - QUERY PLAN +$Q$); + coordinator_plan --------------------------------------------------------------------- Limit -> Sort @@ -955,23 +955,19 @@ EXPLAIN (COSTS FALSE) Group Key: remote_scan.array_length, remote_scan.array_agg -> Custom Scan (Citus Adaptive) Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(13 rows) +(7 rows) -- check the plan if the hash aggreate is disabled. SET enable_hashagg TO off; +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT array_agg(l_linenumber), array_length(array_agg(l_linenumber), 1) FROM lineitem_hash_part GROUP BY l_orderkey ORDER BY 2 LIMIT 15; - QUERY PLAN +$Q$); + coordinator_plan --------------------------------------------------------------------- Limit -> Unique @@ -979,13 +975,7 @@ EXPLAIN (COSTS FALSE) Sort Key: remote_scan.array_length, remote_scan.array_agg -> Custom Scan (Citus Adaptive) Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(12 rows) +(6 rows) SET enable_hashagg TO on; -- distinct on non-partition column with aggregate diff --git a/src/test/regress/expected/multi_select_distinct_1.out b/src/test/regress/expected/multi_select_distinct_1.out deleted file mode 100644 index 506ce215f..000000000 --- a/src/test/regress/expected/multi_select_distinct_1.out +++ /dev/null @@ -1,1569 +0,0 @@ --- --- MULTI_SELECT_DISTINCT --- --- Tests select distinct, and select distinct on features. --- -ANALYZE lineitem_hash_part; --- function calls are supported -SELECT DISTINCT l_orderkey, now() FROM lineitem_hash_part LIMIT 0; - l_orderkey | now ---------------------------------------------------------------------- -(0 rows) - -SELECT DISTINCT l_orderkey, avg(l_linenumber) -FROM lineitem_hash_part -GROUP BY l_orderkey -HAVING avg(l_linenumber) = (select avg(distinct l_linenumber)) -LIMIT 10; -ERROR: Subqueries in HAVING cannot refer to outer query -SELECT DISTINCT l_orderkey -FROM lineitem_hash_part -GROUP BY l_orderkey -HAVING (select avg(distinct l_linenumber) = l_orderkey) -LIMIT 10; -ERROR: Subqueries in HAVING cannot refer to outer query -SELECT DISTINCT l_partkey, 1 + (random() * 0)::int FROM lineitem_hash_part ORDER BY 1 DESC LIMIT 3; - l_partkey | ?column? ---------------------------------------------------------------------- - 199973 | 1 - 199946 | 1 - 199943 | 1 -(3 rows) - --- const expressions are supported -SELECT DISTINCT l_orderkey, 1+1 FROM lineitem_hash_part ORDER BY 1 LIMIT 5; - l_orderkey | ?column? ---------------------------------------------------------------------- - 1 | 2 - 2 | 2 - 3 | 2 - 4 | 2 - 5 | 2 -(5 rows) - --- non const expressions are also supported -SELECT DISTINCT l_orderkey, l_partkey + 1 FROM lineitem_hash_part ORDER BY 1, 2 LIMIT 5; - l_orderkey | ?column? ---------------------------------------------------------------------- - 1 | 2133 - 1 | 15636 - 1 | 24028 - 1 | 63701 - 1 | 67311 -(5 rows) - --- column expressions are supported -SELECT DISTINCT l_orderkey, l_shipinstruct || l_shipmode FROM lineitem_hash_part ORDER BY 2 , 1 LIMIT 5; - l_orderkey | ?column? ---------------------------------------------------------------------- - 32 | COLLECT CODAIR - 39 | COLLECT CODAIR - 66 | COLLECT CODAIR - 70 | COLLECT CODAIR - 98 | COLLECT CODAIR -(5 rows) - --- function calls with const input are supported -SELECT DISTINCT l_orderkey, strpos('AIR', 'A') FROM lineitem_hash_part ORDER BY 1,2 LIMIT 5; - l_orderkey | strpos ---------------------------------------------------------------------- - 1 | 1 - 2 | 1 - 3 | 1 - 4 | 1 - 5 | 1 -(5 rows) - --- function calls with non-const input are supported -SELECT DISTINCT l_orderkey, strpos(l_shipmode, 'I') - FROM lineitem_hash_part - WHERE strpos(l_shipmode, 'I') > 1 - ORDER BY 2, 1 - LIMIT 5; - l_orderkey | strpos ---------------------------------------------------------------------- - 1 | 2 - 3 | 2 - 5 | 2 - 32 | 2 - 33 | 2 -(5 rows) - --- row types are supported -SELECT DISTINCT (l_orderkey, l_partkey) AS pair FROM lineitem_hash_part ORDER BY 1 LIMIT 5; - pair ---------------------------------------------------------------------- - (1,2132) - (1,15635) - (1,24027) - (1,63700) - (1,67310) -(5 rows) - --- distinct on partition column --- verify counts match with respect to count(distinct) -CREATE TEMP TABLE temp_orderkeys AS SELECT DISTINCT l_orderkey FROM lineitem_hash_part; -SELECT COUNT(*) FROM temp_orderkeys; - count ---------------------------------------------------------------------- - 2985 -(1 row) - -SELECT COUNT(DISTINCT l_orderkey) FROM lineitem_hash_part; - count ---------------------------------------------------------------------- - 2985 -(1 row) - -SELECT DISTINCT l_orderkey FROM lineitem_hash_part WHERE l_orderkey < 500 and l_partkey < 5000 order by 1; - l_orderkey ---------------------------------------------------------------------- - 1 - 3 - 32 - 35 - 39 - 65 - 129 - 130 - 134 - 164 - 194 - 228 - 261 - 290 - 320 - 321 - 354 - 418 -(18 rows) - --- distinct on non-partition column -SELECT DISTINCT l_partkey FROM lineitem_hash_part WHERE l_orderkey > 5 and l_orderkey < 20 order by 1; - l_partkey ---------------------------------------------------------------------- - 79251 - 94780 - 139636 - 145243 - 151894 - 157238 - 163073 - 182052 -(8 rows) - -SELECT DISTINCT l_shipmode FROM lineitem_hash_part ORDER BY 1 DESC; - l_shipmode ---------------------------------------------------------------------- - TRUCK - SHIP - REG AIR - RAIL - MAIL - FOB - AIR -(7 rows) - --- distinct with multiple columns -SELECT DISTINCT l_orderkey, o_orderdate - FROM lineitem_hash_part JOIN orders_hash_part ON (l_orderkey = o_orderkey) - WHERE l_orderkey < 10 - ORDER BY l_orderkey; - l_orderkey | o_orderdate ---------------------------------------------------------------------- - 1 | 01-02-1996 - 2 | 12-01-1996 - 3 | 10-14-1993 - 4 | 10-11-1995 - 5 | 07-30-1994 - 6 | 02-21-1992 - 7 | 01-10-1996 -(7 rows) - --- distinct on partition column with aggregate --- this is the same as the one without distinct due to group by -SELECT DISTINCT l_orderkey, count(*) - FROM lineitem_hash_part - WHERE l_orderkey < 200 - GROUP BY 1 - HAVING count(*) > 5 - ORDER BY 2 DESC, 1; - l_orderkey | count ---------------------------------------------------------------------- - 7 | 7 - 68 | 7 - 129 | 7 - 164 | 7 - 194 | 7 - 1 | 6 - 3 | 6 - 32 | 6 - 35 | 6 - 39 | 6 - 67 | 6 - 69 | 6 - 70 | 6 - 71 | 6 - 134 | 6 - 135 | 6 - 163 | 6 - 192 | 6 - 197 | 6 -(19 rows) - --- explain the query to see actual plan -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_orderkey, count(*) - FROM lineitem_hash_part - WHERE l_orderkey < 200 - GROUP BY 1 - HAVING count(*) > 5 - ORDER BY 2 DESC, 1; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: remote_scan.count DESC, remote_scan.l_orderkey - -> HashAggregate - Group Key: remote_scan.count, remote_scan.l_orderkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_orderkey - Filter: (count(*) > 5) - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part - Filter: (l_orderkey < 200) -(14 rows) - --- check the plan if the hash aggreate is disabled -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_orderkey, count(*) - FROM lineitem_hash_part - WHERE l_orderkey < 200 - GROUP BY 1 - HAVING count(*) > 5 - ORDER BY 2 DESC, 1; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: remote_scan.count DESC, remote_scan.l_orderkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_orderkey - Filter: (count(*) > 5) - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part - Filter: (l_orderkey < 200) -(13 rows) - -SET enable_hashagg TO on; --- distinct on aggregate of group by columns, we try to check whether we handle --- queries which does not have any group by column in distinct columns properly. -SELECT DISTINCT count(*) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1; - count ---------------------------------------------------------------------- - 1 - 2 - 3 - 4 -(4 rows) - --- explain the query to see actual plan. We expect to see Aggregate node having --- group by key on count(*) column, since columns in the Group By doesn't guarantee --- the uniqueness of the result. -EXPLAIN (COSTS FALSE) - SELECT DISTINCT count(*) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: (COALESCE((pg_catalog.sum(remote_scan.count))::bigint, '0'::bigint)) - -> HashAggregate - Group Key: remote_scan.worker_column_2, remote_scan.worker_column_3 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(13 rows) - --- check the plan if the hash aggreate is disabled. We expect to see sort+unique --- instead of aggregate plan node to handle distinct. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT count(*) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: (COALESCE((pg_catalog.sum(remote_scan.count))::bigint, '0'::bigint)) - -> GroupAggregate - Group Key: remote_scan.worker_column_2, remote_scan.worker_column_3 - -> Sort - Sort Key: remote_scan.worker_column_2, remote_scan.worker_column_3 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(15 rows) - -SET enable_hashagg TO on; --- Now we have only part of group clause columns in distinct, yet it is still not --- enough to use Group By columns to guarantee uniqueness of result list. -SELECT DISTINCT l_suppkey, count(*) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - l_suppkey | count ---------------------------------------------------------------------- - 1 | 1 - 2 | 1 - 3 | 1 - 4 | 1 - 5 | 1 - 7 | 1 - 10 | 1 - 12 | 1 - 13 | 1 - 14 | 1 -(10 rows) - --- explain the query to see actual plan. Similar to the explain of the query above. -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_suppkey, count(*) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_suppkey, (COALESCE((pg_catalog.sum(remote_scan.count))::bigint, '0'::bigint)) - -> HashAggregate - Group Key: remote_scan.l_suppkey, remote_scan.worker_column_3 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- check the plan if the hash aggreate is disabled. Similar to the explain of --- the query above. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_suppkey, count(*) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_suppkey, (COALESCE((pg_catalog.sum(remote_scan.count))::bigint, '0'::bigint)) - -> GroupAggregate - Group Key: remote_scan.l_suppkey, remote_scan.worker_column_3 - -> Sort - Sort Key: remote_scan.l_suppkey, remote_scan.worker_column_3 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - -SET enable_hashagg TO on; --- Similar to the above query, not with count but avg. Only difference with the --- above query is that, we create run two aggregate functions in workers. -SELECT DISTINCT l_suppkey, avg(l_partkey) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1,2 - LIMIT 10; - l_suppkey | avg ---------------------------------------------------------------------- - 1 | 190000.000000000000 - 2 | 172450.000000000000 - 3 | 112469.000000000000 - 3 | 134976.000000000000 - 4 | 112470.000000000000 - 4 | 142461.000000000000 - 5 | 182450.000000000000 - 7 | 137493.000000000000 - 10 | 150009.000000000000 - 12 | 17510.0000000000000000 -(10 rows) - --- explain the query to see actual plan. Similar to the explain of the query above. --- Only aggregate functions will be changed. -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_suppkey, avg(l_partkey) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1,2 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_suppkey, ((pg_catalog.sum(remote_scan.avg) / pg_catalog.sum(remote_scan.avg_1))) - -> HashAggregate - Group Key: remote_scan.l_suppkey, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- check the plan if the hash aggreate is disabled. This explain errors out due --- to a bug right now, expectation must be corrected after fixing it. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_suppkey, avg(l_partkey) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1,2 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_suppkey, ((pg_catalog.sum(remote_scan.avg) / pg_catalog.sum(remote_scan.avg_1))) - -> GroupAggregate - Group Key: remote_scan.l_suppkey, remote_scan.worker_column_4 - -> Sort - Sort Key: remote_scan.l_suppkey, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - -SET enable_hashagg TO on; --- Similar to the above query but with distinct on -SELECT DISTINCT ON (l_suppkey) avg(l_partkey) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY l_suppkey,1 - LIMIT 10; - avg ---------------------------------------------------------------------- - 190000.000000000000 - 172450.000000000000 - 112469.000000000000 - 112470.000000000000 - 182450.000000000000 - 137493.000000000000 - 150009.000000000000 - 17510.0000000000000000 - 87504.000000000000 - 77506.000000000000 -(10 rows) - --- explain the query to see actual plan. We expect to see sort+unique to handle --- distinct on. -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (l_suppkey) avg(l_partkey) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY l_suppkey,1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.worker_column_3, ((pg_catalog.sum(remote_scan.avg) / pg_catalog.sum(remote_scan.avg_1))) - -> HashAggregate - Group Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- check the plan if the hash aggreate is disabled. We expect to see sort+unique to --- handle distinct on. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (l_suppkey) avg(l_partkey) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY l_suppkey,1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.worker_column_3, ((pg_catalog.sum(remote_scan.avg) / pg_catalog.sum(remote_scan.avg_1))) - -> GroupAggregate - Group Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Sort - Sort Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - -SET enable_hashagg TO on; --- distinct with expression and aggregation -SELECT DISTINCT avg(ceil(l_partkey / 2)) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - avg ---------------------------------------------------------------------- - 9 - 39 - 74 - 87 - 89 - 91 - 97 - 102 - 111 - 122 -(10 rows) - --- explain the query to see actual plan -EXPLAIN (COSTS FALSE) - SELECT DISTINCT avg(ceil(l_partkey / 2)) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: ((sum(remote_scan.avg) / (pg_catalog.sum(remote_scan.avg_1))::double precision)) - -> HashAggregate - Group Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- check the plan if the hash aggreate is disabled. This explain errors out due --- to a bug right now, expectation must be corrected after fixing it. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT avg(ceil(l_partkey / 2)) - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: ((sum(remote_scan.avg) / (pg_catalog.sum(remote_scan.avg_1))::double precision)) - -> GroupAggregate - Group Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Sort - Sort Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - -SET enable_hashagg TO on; --- expression among aggregations. -SELECT DISTINCT sum(l_suppkey) + count(l_partkey) AS dis - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - dis ---------------------------------------------------------------------- - 2 - 3 - 4 - 5 - 6 - 8 - 11 - 13 - 14 - 15 -(10 rows) - --- explain the query to see actual plan -EXPLAIN (COSTS FALSE) - SELECT DISTINCT sum(l_suppkey) + count(l_partkey) AS dis - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: (((pg_catalog.sum(remote_scan.dis))::bigint + COALESCE((pg_catalog.sum(remote_scan.dis_1))::bigint, '0'::bigint))) - -> HashAggregate - Group Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- check the plan if the hash aggreate is disabled. This explain errors out due --- to a bug right now, expectation must be corrected after fixing it. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT sum(l_suppkey) + count(l_partkey) AS dis - FROM lineitem_hash_part - GROUP BY l_suppkey, l_linenumber - ORDER BY 1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: (((pg_catalog.sum(remote_scan.dis))::bigint + COALESCE((pg_catalog.sum(remote_scan.dis_1))::bigint, '0'::bigint))) - -> GroupAggregate - Group Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Sort - Sort Key: remote_scan.worker_column_3, remote_scan.worker_column_4 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey, l_linenumber - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - -SET enable_hashagg TO on; --- distinct on all columns, note Group By columns guarantees uniqueness of the --- result list. -SELECT DISTINCT * - FROM lineitem_hash_part - GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 - ORDER BY 1,2 - LIMIT 10; - l_orderkey | l_partkey | l_suppkey | l_linenumber | l_quantity | l_extendedprice | l_discount | l_tax | l_returnflag | l_linestatus | l_shipdate | l_commitdate | l_receiptdate | l_shipinstruct | l_shipmode | l_comment ---------------------------------------------------------------------- - 1 | 2132 | 4633 | 4 | 28.00 | 28955.64 | 0.09 | 0.06 | N | O | 04-21-1996 | 03-30-1996 | 05-16-1996 | NONE | AIR | lites. fluffily even de - 1 | 15635 | 638 | 6 | 32.00 | 49620.16 | 0.07 | 0.02 | N | O | 01-30-1996 | 02-07-1996 | 02-03-1996 | DELIVER IN PERSON | MAIL | arefully slyly ex - 1 | 24027 | 1534 | 5 | 24.00 | 22824.48 | 0.10 | 0.04 | N | O | 03-30-1996 | 03-14-1996 | 04-01-1996 | NONE | FOB | pending foxes. slyly re - 1 | 63700 | 3701 | 3 | 8.00 | 13309.60 | 0.10 | 0.02 | N | O | 01-29-1996 | 03-05-1996 | 01-31-1996 | TAKE BACK RETURN | REG AIR | riously. regular, express dep - 1 | 67310 | 7311 | 2 | 36.00 | 45983.16 | 0.09 | 0.06 | N | O | 04-12-1996 | 02-28-1996 | 04-20-1996 | TAKE BACK RETURN | MAIL | ly final dependencies: slyly bold - 1 | 155190 | 7706 | 1 | 17.00 | 21168.23 | 0.04 | 0.02 | N | O | 03-13-1996 | 02-12-1996 | 03-22-1996 | DELIVER IN PERSON | TRUCK | egular courts above the - 2 | 106170 | 1191 | 1 | 38.00 | 44694.46 | 0.00 | 0.05 | N | O | 01-28-1997 | 01-14-1997 | 02-02-1997 | TAKE BACK RETURN | RAIL | ven requests. deposits breach a - 3 | 4297 | 1798 | 1 | 45.00 | 54058.05 | 0.06 | 0.00 | R | F | 02-02-1994 | 01-04-1994 | 02-23-1994 | NONE | AIR | ongside of the furiously brave acco - 3 | 19036 | 6540 | 2 | 49.00 | 46796.47 | 0.10 | 0.00 | R | F | 11-09-1993 | 12-20-1993 | 11-24-1993 | TAKE BACK RETURN | RAIL | unusual accounts. eve - 3 | 29380 | 1883 | 4 | 2.00 | 2618.76 | 0.01 | 0.06 | A | F | 12-04-1993 | 01-07-1994 | 01-01-1994 | NONE | TRUCK | y. fluffily pending d -(10 rows) - --- explain the query to see actual plan. We expect to see only one aggregation --- node since group by columns guarantees the uniqueness. -EXPLAIN (COSTS FALSE) - SELECT DISTINCT * - FROM lineitem_hash_part - GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 - ORDER BY 1,2 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.l_orderkey, remote_scan.l_partkey - -> HashAggregate - Group Key: remote_scan.l_orderkey, remote_scan.l_partkey, remote_scan.l_suppkey, remote_scan.l_linenumber, remote_scan.l_quantity, remote_scan.l_extendedprice, remote_scan.l_discount, remote_scan.l_tax, remote_scan.l_returnflag, remote_scan.l_linestatus, remote_scan.l_shipdate, remote_scan.l_commitdate, remote_scan.l_receiptdate, remote_scan.l_shipinstruct, remote_scan.l_shipmode, remote_scan.l_comment - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Unique - -> Group - Group Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Sort - Sort Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(17 rows) - --- check the plan if the hash aggreate is disabled. We expect to see only one --- aggregation node since group by columns guarantees the uniqueness. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT * - FROM lineitem_hash_part - GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 - ORDER BY 1,2 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_orderkey, remote_scan.l_partkey, remote_scan.l_suppkey, remote_scan.l_linenumber, remote_scan.l_quantity, remote_scan.l_extendedprice, remote_scan.l_discount, remote_scan.l_tax, remote_scan.l_returnflag, remote_scan.l_linestatus, remote_scan.l_shipdate, remote_scan.l_commitdate, remote_scan.l_receiptdate, remote_scan.l_shipinstruct, remote_scan.l_shipmode, remote_scan.l_comment - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Unique - -> Group - Group Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Sort - Sort Key: l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - -SET enable_hashagg TO on; --- distinct on count distinct -SELECT DISTINCT count(DISTINCT l_partkey), count(DISTINCT l_shipmode) - FROM lineitem_hash_part - GROUP BY l_orderkey - ORDER BY 1,2; - count | count ---------------------------------------------------------------------- - 1 | 1 - 2 | 1 - 2 | 2 - 3 | 1 - 3 | 2 - 3 | 3 - 4 | 1 - 4 | 2 - 4 | 3 - 4 | 4 - 5 | 2 - 5 | 3 - 5 | 4 - 5 | 5 - 6 | 2 - 6 | 3 - 6 | 4 - 6 | 5 - 6 | 6 - 7 | 2 - 7 | 3 - 7 | 4 - 7 | 5 - 7 | 6 - 7 | 7 -(25 rows) - --- explain the query to see actual plan. We expect to see aggregation plan for --- the outer distinct. -EXPLAIN (COSTS FALSE) - SELECT DISTINCT count(DISTINCT l_partkey), count(DISTINCT l_shipmode) - FROM lineitem_hash_part - GROUP BY l_orderkey - ORDER BY 1,2; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: remote_scan.count, remote_scan.count_1 - -> HashAggregate - Group Key: remote_scan.count, remote_scan.count_1 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> GroupAggregate - Group Key: l_orderkey - -> Sort - Sort Key: l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- check the plan if the hash aggreate is disabled. We expect to see sort + unique --- plans for the outer distinct. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT count(DISTINCT l_partkey), count(DISTINCT l_shipmode) - FROM lineitem_hash_part - GROUP BY l_orderkey - ORDER BY 1,2; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: remote_scan.count, remote_scan.count_1 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> GroupAggregate - Group Key: l_orderkey - -> Sort - Sort Key: l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(13 rows) - -SET enable_hashagg TO on; --- distinct on aggregation with filter and expression -SELECT DISTINCT ceil(count(case when l_partkey > 100000 THEN 1 ELSE 0 END) / 2) AS count - FROM lineitem_hash_part - GROUP BY l_suppkey - ORDER BY 1; - count ---------------------------------------------------------------------- - 0 - 1 - 2 - 3 - 4 -(5 rows) - --- explain the query to see actual plan -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ceil(count(case when l_partkey > 100000 THEN 1 ELSE 0 END) / 2) AS count - FROM lineitem_hash_part - GROUP BY l_suppkey - ORDER BY 1; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: (ceil(((COALESCE((pg_catalog.sum(remote_scan.count))::bigint, '0'::bigint) / 2))::double precision)) - -> HashAggregate - Group Key: remote_scan.worker_column_2 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(13 rows) - --- check the plan if the hash aggreate is disabled -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ceil(count(case when l_partkey > 100000 THEN 1 ELSE 0 END) / 2) AS count - FROM lineitem_hash_part - GROUP BY l_suppkey - ORDER BY 1; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: (ceil(((COALESCE((pg_catalog.sum(remote_scan.count))::bigint, '0'::bigint) / 2))::double precision)) - -> GroupAggregate - Group Key: remote_scan.worker_column_2 - -> Sort - Sort Key: remote_scan.worker_column_2 - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_suppkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(15 rows) - -SET enable_hashagg TO on; --- explain the query to see actual plan with array_agg aggregation. -EXPLAIN (COSTS FALSE) - SELECT DISTINCT array_agg(l_linenumber), array_length(array_agg(l_linenumber), 1) - FROM lineitem_hash_part - GROUP BY l_orderkey - ORDER BY 2 - LIMIT 15; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.array_length - -> HashAggregate - Group Key: remote_scan.array_length, remote_scan.array_agg - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> GroupAggregate - Group Key: l_orderkey - -> Sort - Sort Key: l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(15 rows) - --- check the plan if the hash aggreate is disabled. -SET enable_hashagg TO off; -EXPLAIN (COSTS FALSE) - SELECT DISTINCT array_agg(l_linenumber), array_length(array_agg(l_linenumber), 1) - FROM lineitem_hash_part - GROUP BY l_orderkey - ORDER BY 2 - LIMIT 15; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.array_length, remote_scan.array_agg - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> GroupAggregate - Group Key: l_orderkey - -> Sort - Sort Key: l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - -SET enable_hashagg TO on; --- distinct on non-partition column with aggregate --- this is the same as non-distinct version due to group by -SELECT DISTINCT l_partkey, count(*) - FROM lineitem_hash_part - GROUP BY 1 - HAVING count(*) > 2 - ORDER BY 1; - l_partkey | count ---------------------------------------------------------------------- - 1051 | 3 - 1927 | 3 - 6983 | 3 - 15283 | 3 - 87761 | 3 - 136884 | 3 - 149926 | 3 - 160895 | 3 - 177771 | 3 - 188804 | 3 - 199146 | 3 -(11 rows) - --- explain the query to see actual plan -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_partkey, count(*) - FROM lineitem_hash_part - GROUP BY 1 - HAVING count(*) > 2 - ORDER BY 1; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: remote_scan.l_partkey, (COALESCE((pg_catalog.sum(remote_scan.count))::bigint, '0'::bigint)) - -> HashAggregate - Group Key: remote_scan.l_partkey - Filter: (COALESCE((pg_catalog.sum(remote_scan.worker_column_3))::bigint, '0'::bigint) > 2) - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> HashAggregate - Group Key: l_partkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- distinct on non-partition column and avg -SELECT DISTINCT l_partkey, avg(l_linenumber) - FROM lineitem_hash_part - WHERE l_partkey < 500 - GROUP BY 1 - HAVING avg(l_linenumber) > 2 - ORDER BY 1; - l_partkey | avg ---------------------------------------------------------------------- - 18 | 7.0000000000000000 - 79 | 6.0000000000000000 - 149 | 4.5000000000000000 - 175 | 5.0000000000000000 - 179 | 6.0000000000000000 - 182 | 3.0000000000000000 - 222 | 4.0000000000000000 - 278 | 3.0000000000000000 - 299 | 7.0000000000000000 - 308 | 7.0000000000000000 - 309 | 5.0000000000000000 - 321 | 3.0000000000000000 - 337 | 6.0000000000000000 - 364 | 3.0000000000000000 - 403 | 4.0000000000000000 -(15 rows) - --- distinct on multiple non-partition columns -SELECT DISTINCT l_partkey, l_suppkey - FROM lineitem_hash_part - WHERE l_shipmode = 'AIR' AND l_orderkey < 100 - ORDER BY 1, 2; - l_partkey | l_suppkey ---------------------------------------------------------------------- - 2132 | 4633 - 4297 | 1798 - 37531 | 35 - 44161 | 6666 - 44706 | 4707 - 67831 | 5350 - 85811 | 8320 - 94368 | 6878 - 108338 | 849 - 108570 | 8571 - 137267 | 4807 - 137469 | 9983 - 173489 | 3490 - 196156 | 1195 - 197921 | 441 -(15 rows) - -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_partkey, l_suppkey - FROM lineitem_hash_part - WHERE l_shipmode = 'AIR' AND l_orderkey < 100 - ORDER BY 1, 2; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: remote_scan.l_partkey, remote_scan.l_suppkey - -> HashAggregate - Group Key: remote_scan.l_partkey, remote_scan.l_suppkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Unique - -> Sort - Sort Key: l_partkey, l_suppkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part - Filter: ((l_orderkey < 100) AND (l_shipmode = 'AIR'::bpchar)) -(14 rows) - --- distinct on partition column -SELECT DISTINCT ON (l_orderkey) l_orderkey, l_partkey, l_suppkey - FROM lineitem_hash_part - WHERE l_orderkey < 35 - ORDER BY 1, 2, 3; - l_orderkey | l_partkey | l_suppkey ---------------------------------------------------------------------- - 1 | 2132 | 4633 - 2 | 106170 | 1191 - 3 | 4297 | 1798 - 4 | 88035 | 5560 - 5 | 37531 | 35 - 6 | 139636 | 2150 - 7 | 79251 | 1759 - 32 | 2743 | 7744 - 33 | 33918 | 3919 - 34 | 88362 | 871 -(10 rows) - -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (l_orderkey) l_orderkey, l_partkey, l_suppkey - FROM lineitem_hash_part - WHERE l_orderkey < 35 - ORDER BY 1, 2, 3; - QUERY PLAN ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: remote_scan.l_orderkey, remote_scan.l_partkey, remote_scan.l_suppkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Unique - -> Sort - Sort Key: l_orderkey, l_partkey, l_suppkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part - Filter: (l_orderkey < 35) -(13 rows) - --- distinct on non-partition column --- note order by is required here --- otherwise query results will be different since --- distinct on clause is on non-partition column -SELECT DISTINCT ON (l_partkey) l_partkey, l_orderkey - FROM lineitem_hash_part - ORDER BY 1,2 - LIMIT 20; - l_partkey | l_orderkey ---------------------------------------------------------------------- - 18 | 12005 - 79 | 5121 - 91 | 2883 - 149 | 807 - 175 | 4102 - 179 | 2117 - 182 | 548 - 195 | 2528 - 204 | 10048 - 222 | 9413 - 245 | 9446 - 278 | 1287 - 299 | 1122 - 308 | 11137 - 309 | 2374 - 318 | 321 - 321 | 5984 - 337 | 10403 - 350 | 13698 - 358 | 4323 -(20 rows) - -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (l_partkey) l_partkey, l_orderkey - FROM lineitem_hash_part - ORDER BY 1,2 - LIMIT 20; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_partkey, remote_scan.l_orderkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Unique - -> Sort - Sort Key: l_partkey, l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(14 rows) - --- distinct on with joins --- each customer's first order key -SELECT DISTINCT ON (o_custkey) o_custkey, l_orderkey - FROM lineitem_hash_part JOIN orders_hash_part ON (l_orderkey = o_orderkey) - WHERE o_custkey < 15 - ORDER BY 1,2; - o_custkey | l_orderkey ---------------------------------------------------------------------- - 1 | 9154 - 2 | 10563 - 4 | 320 - 5 | 11682 - 7 | 10402 - 8 | 102 - 10 | 1602 - 11 | 12800 - 13 | 994 - 14 | 11011 -(10 rows) - -SELECT coordinator_plan($Q$ -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (o_custkey) o_custkey, l_orderkey - FROM lineitem_hash_part JOIN orders_hash_part ON (l_orderkey = o_orderkey) - WHERE o_custkey < 15 - ORDER BY 1,2; -$Q$); - coordinator_plan ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: remote_scan.o_custkey, remote_scan.l_orderkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 -(5 rows) - --- explain without order by --- notice master plan has order by on distinct on column -SELECT coordinator_plan($Q$ -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (o_custkey) o_custkey, l_orderkey - FROM lineitem_hash_part JOIN orders_hash_part ON (l_orderkey = o_orderkey) - WHERE o_custkey < 15; -$Q$); - coordinator_plan ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: remote_scan.o_custkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 -(5 rows) - --- each customer's each order's first l_partkey -SELECT DISTINCT ON (o_custkey, l_orderkey) o_custkey, l_orderkey, l_linenumber, l_partkey - FROM lineitem_hash_part JOIN orders_hash_part ON (l_orderkey = o_orderkey) - WHERE o_custkey < 20 - ORDER BY 1,2,3; - o_custkey | l_orderkey | l_linenumber | l_partkey ---------------------------------------------------------------------- - 1 | 9154 | 1 | 86513 - 1 | 14656 | 1 | 59539 - 2 | 10563 | 1 | 147459 - 4 | 320 | 1 | 4415 - 4 | 739 | 1 | 84489 - 4 | 10688 | 1 | 45037 - 4 | 10788 | 1 | 50814 - 4 | 13728 | 1 | 86216 - 5 | 11682 | 1 | 31634 - 5 | 11746 | 1 | 180724 - 5 | 14308 | 1 | 157430 - 7 | 10402 | 1 | 53661 - 7 | 13031 | 1 | 112161 - 7 | 14145 | 1 | 138729 - 7 | 14404 | 1 | 143034 - 8 | 102 | 1 | 88914 - 8 | 164 | 1 | 91309 - 8 | 13601 | 1 | 40504 - 10 | 1602 | 1 | 182806 - 10 | 9862 | 1 | 86241 - 10 | 11431 | 1 | 62112 - 10 | 13124 | 1 | 29414 - 11 | 12800 | 1 | 152806 - 13 | 994 | 1 | 64486 - 13 | 1603 | 1 | 38191 - 13 | 4704 | 1 | 77934 - 13 | 9927 | 1 | 875 - 14 | 11011 | 1 | 172485 - 17 | 896 | 1 | 38675 - 17 | 5507 | 1 | 9600 - 19 | 353 | 1 | 119305 - 19 | 1504 | 1 | 81389 - 19 | 1669 | 1 | 78373 - 19 | 5893 | 1 | 133707 - 19 | 9954 | 1 | 92138 - 19 | 14885 | 1 | 36154 -(36 rows) - --- explain without order by -SELECT coordinator_plan($Q$ -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (o_custkey, l_orderkey) o_custkey, l_orderkey, l_linenumber, l_partkey - FROM lineitem_hash_part JOIN orders_hash_part ON (l_orderkey = o_orderkey) - WHERE o_custkey < 20; -$Q$); - coordinator_plan ---------------------------------------------------------------------- - Unique - -> Sort - Sort Key: remote_scan.o_custkey, remote_scan.l_orderkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 -(5 rows) - --- each customer's each order's last l_partkey -SELECT DISTINCT ON (o_custkey, l_orderkey) o_custkey, l_orderkey, l_linenumber, l_partkey - FROM lineitem_hash_part JOIN orders_hash_part ON (l_orderkey = o_orderkey) - WHERE o_custkey < 15 - ORDER BY 1,2,3 DESC; - o_custkey | l_orderkey | l_linenumber | l_partkey ---------------------------------------------------------------------- - 1 | 9154 | 7 | 173448 - 1 | 14656 | 1 | 59539 - 2 | 10563 | 4 | 110741 - 4 | 320 | 2 | 192158 - 4 | 739 | 5 | 187523 - 4 | 10688 | 2 | 132574 - 4 | 10788 | 4 | 196473 - 4 | 13728 | 3 | 12450 - 5 | 11682 | 3 | 177152 - 5 | 11746 | 7 | 193807 - 5 | 14308 | 3 | 140916 - 7 | 10402 | 2 | 64514 - 7 | 13031 | 6 | 7761 - 7 | 14145 | 6 | 130723 - 7 | 14404 | 7 | 35349 - 8 | 102 | 4 | 61158 - 8 | 164 | 7 | 3037 - 8 | 13601 | 5 | 12470 - 10 | 1602 | 1 | 182806 - 10 | 9862 | 5 | 135675 - 10 | 11431 | 7 | 8563 - 10 | 13124 | 3 | 67055 - 11 | 12800 | 5 | 179110 - 13 | 994 | 4 | 130471 - 13 | 1603 | 2 | 65209 - 13 | 4704 | 3 | 63081 - 13 | 9927 | 6 | 119356 - 14 | 11011 | 7 | 95939 -(28 rows) - --- subqueries -SELECT DISTINCT l_orderkey, l_partkey - FROM ( - SELECT l_orderkey, l_partkey - FROM lineitem_hash_part - ) q - ORDER BY 1,2 - LIMIT 10; - l_orderkey | l_partkey ---------------------------------------------------------------------- - 1 | 2132 - 1 | 15635 - 1 | 24027 - 1 | 63700 - 1 | 67310 - 1 | 155190 - 2 | 106170 - 3 | 4297 - 3 | 19036 - 3 | 29380 -(10 rows) - -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_orderkey, l_partkey - FROM ( - SELECT l_orderkey, l_partkey - FROM lineitem_hash_part - ) q - ORDER BY 1,2 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.l_orderkey, remote_scan.l_partkey - -> HashAggregate - Group Key: remote_scan.l_orderkey, remote_scan.l_partkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Sort - Sort Key: l_orderkey, l_partkey - -> HashAggregate - Group Key: l_orderkey, l_partkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - -SELECT DISTINCT l_orderkey, cnt - FROM ( - SELECT l_orderkey, count(*) as cnt - FROM lineitem_hash_part - GROUP BY 1 - ) q - ORDER BY 1,2 - LIMIT 10; - l_orderkey | cnt ---------------------------------------------------------------------- - 1 | 6 - 2 | 1 - 3 | 6 - 4 | 1 - 5 | 3 - 6 | 1 - 7 | 7 - 32 | 6 - 33 | 4 - 34 | 3 -(10 rows) - -EXPLAIN (COSTS FALSE) - SELECT DISTINCT l_orderkey, cnt - FROM ( - SELECT l_orderkey, count(*) as cnt - FROM lineitem_hash_part - GROUP BY 1 - ) q - ORDER BY 1,2 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Sort - Sort Key: remote_scan.l_orderkey, remote_scan.cnt - -> HashAggregate - Group Key: remote_scan.l_orderkey, remote_scan.cnt - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Sort - Sort Key: lineitem_hash_part.l_orderkey, (count(*)) - -> HashAggregate - Group Key: lineitem_hash_part.l_orderkey, count(*) - -> HashAggregate - Group Key: lineitem_hash_part.l_orderkey - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(18 rows) - --- distinct on partition column --- random() is added to inner query to prevent flattening -SELECT DISTINCT ON (l_orderkey) l_orderkey, l_partkey - FROM ( - SELECT l_orderkey, l_partkey, (random()*10)::int + 2 as r - FROM lineitem_hash_part - ) q - WHERE r > 1 - ORDER BY 1,2 - LIMIT 10; - l_orderkey | l_partkey ---------------------------------------------------------------------- - 1 | 2132 - 2 | 106170 - 3 | 4297 - 4 | 88035 - 5 | 37531 - 6 | 139636 - 7 | 79251 - 32 | 2743 - 33 | 33918 - 34 | 88362 -(10 rows) - -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (l_orderkey) l_orderkey, l_partkey - FROM ( - SELECT l_orderkey, l_partkey, (random()*10)::int + 2 as r - FROM lineitem_hash_part - ) q - WHERE r > 1 - ORDER BY 1,2 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_orderkey, remote_scan.l_partkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Unique - -> Sort - Sort Key: q.l_orderkey, q.l_partkey - -> Subquery Scan on q - Filter: (q.r > 1) - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - --- distinct on non-partition column -SELECT DISTINCT ON (l_partkey) l_orderkey, l_partkey - FROM ( - SELECT l_orderkey, l_partkey, (random()*10)::int + 2 as r - FROM lineitem_hash_part - ) q - WHERE r > 1 - ORDER BY 2,1 - LIMIT 10; - l_orderkey | l_partkey ---------------------------------------------------------------------- - 12005 | 18 - 5121 | 79 - 2883 | 91 - 807 | 149 - 4102 | 175 - 2117 | 179 - 548 | 182 - 2528 | 195 - 10048 | 204 - 9413 | 222 -(10 rows) - -EXPLAIN (COSTS FALSE) - SELECT DISTINCT ON (l_partkey) l_orderkey, l_partkey - FROM ( - SELECT l_orderkey, l_partkey, (random()*10)::int + 2 as r - FROM lineitem_hash_part - ) q - WHERE r > 1 - ORDER BY 2,1 - LIMIT 10; - QUERY PLAN ---------------------------------------------------------------------- - Limit - -> Unique - -> Sort - Sort Key: remote_scan.l_partkey, remote_scan.l_orderkey - -> Custom Scan (Citus Adaptive) - Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Limit - -> Unique - -> Sort - Sort Key: q.l_partkey, q.l_orderkey - -> Subquery Scan on q - Filter: (q.r > 1) - -> Seq Scan on lineitem_hash_part_360041 lineitem_hash_part -(16 rows) - diff --git a/src/test/regress/expected/multi_tenant_isolation.out b/src/test/regress/expected/multi_tenant_isolation.out index d851f3c13..5190803c5 100644 --- a/src/test/regress/expected/multi_tenant_isolation.out +++ b/src/test/regress/expected/multi_tenant_isolation.out @@ -669,7 +669,7 @@ SELECT count(*) FROM lineitem_date WHERE l_shipdate = '1997-08-08'; SET search_path to "Tenant Isolation"; UPDATE pg_dist_shard_placement SET shardstate = 3 WHERE nodeport = :worker_1_port; SELECT isolate_tenant_to_new_shard('lineitem_date', '1997-08-08', shard_transfer_mode => 'block_writes'); -ERROR: cannot split shard because relation "lineitem_date" has an inactive shard placement for the shard xxxxx +ERROR: cannot isolate tenant because relation "lineitem_date" has an inactive shard placement for the shard xxxxx HINT: Use master_copy_shard_placement UDF to repair the inactive shard placement. UPDATE pg_dist_shard_placement SET shardstate = 1 WHERE nodeport = :worker_1_port; \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 a16aefb75..e509ad8f4 100644 --- a/src/test/regress/expected/multi_tenant_isolation_nonblocking.out +++ b/src/test/regress/expected/multi_tenant_isolation_nonblocking.out @@ -669,7 +669,7 @@ SELECT count(*) FROM lineitem_date WHERE l_shipdate = '1997-08-08'; SET search_path to "Tenant Isolation"; UPDATE pg_dist_shard_placement SET shardstate = 3 WHERE nodeport = :worker_1_port; SELECT isolate_tenant_to_new_shard('lineitem_date', '1997-08-08', shard_transfer_mode => 'force_logical'); -ERROR: cannot split shard because relation "lineitem_date" has an inactive shard placement for the shard xxxxx +ERROR: cannot isolate tenant because relation "lineitem_date" has an inactive shard placement for the shard xxxxx HINT: Use master_copy_shard_placement UDF to repair the inactive shard placement. UPDATE pg_dist_shard_placement SET shardstate = 1 WHERE nodeport = :worker_1_port; \c - mx_isolation_role_ent - :master_port diff --git a/src/test/regress/expected/multi_test_helpers.out b/src/test/regress/expected/multi_test_helpers.out index 5ae18f26b..b02cd6cd4 100644 --- a/src/test/regress/expected/multi_test_helpers.out +++ b/src/test/regress/expected/multi_test_helpers.out @@ -17,10 +17,15 @@ BEGIN END; $$LANGUAGE plpgsql; -- Create a function to ignore worker plans in explain output +-- Also remove extra "-> Result" lines for PG15 support CREATE OR REPLACE FUNCTION coordinator_plan(explain_command text, out query_plan text) RETURNS SETOF TEXT AS $$ BEGIN FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') + THEN + CONTINUE; + END IF; RETURN next; IF query_plan LIKE '%Task Count:%' THEN @@ -29,6 +34,65 @@ BEGIN END LOOP; RETURN; END; $$ language plpgsql; +-- Create a function to ignore worker plans in explain output +-- It also shows task count for plan and subplans +-- Also remove extra "-> Result" lines for PG15 support +CREATE OR REPLACE FUNCTION coordinator_plan_with_subplans(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +DECLARE + task_count_line_reached boolean := false; +BEGIN + FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') THEN + CONTINUE; + END IF; + IF NOT task_count_line_reached THEN + RETURN next; + END IF; + IF query_plan LIKE '%Task Count:%' THEN + IF NOT task_count_line_reached THEN + SELECT true INTO task_count_line_reached; + ELSE + RETURN next; + END IF; + END IF; + END LOOP; + RETURN; +END; $$ language plpgsql; +-- Create a function to ignore "-> Result" lines for PG15 support +-- In PG15 there are some extra "-> Result" lines +CREATE OR REPLACE FUNCTION plan_without_result_lines(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +BEGIN + FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') THEN + CONTINUE; + END IF; + RETURN next; + END LOOP; + RETURN; +END; $$ language plpgsql; +-- Create a function to normalize Memory Usage, Buckets, Batches +CREATE OR REPLACE FUNCTION plan_normalize_memory(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +BEGIN + FOR query_plan IN execute explain_command LOOP + query_plan := regexp_replace(query_plan, '(Memory( Usage)?|Buckets|Batches): \S*', '\1: xxx', 'g'); + RETURN NEXT; + END LOOP; +END; $$ language plpgsql; +-- Create a function to remove arrows from the explain plan +CREATE OR REPLACE FUNCTION plan_without_arrows(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +BEGIN + FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') THEN + CONTINUE; + END IF; + query_plan := regexp_replace(query_plan, '( )*-> (.*)', '\2', 'g'); + RETURN NEXT; + END LOOP; +END; $$ language plpgsql; -- helper function that returns true if output of given explain has "is not null" (case in-sensitive) CREATE OR REPLACE FUNCTION explain_has_is_not_null(explain_command text) RETURNS BOOLEAN AS $$ diff --git a/src/test/regress/expected/multi_transaction_recovery.out b/src/test/regress/expected/multi_transaction_recovery.out index ad5f5e699..1e4cea224 100644 --- a/src/test/regress/expected/multi_transaction_recovery.out +++ b/src/test/regress/expected/multi_transaction_recovery.out @@ -352,7 +352,10 @@ SELECT recover_prepared_transactions(); 0 (1 row) -SELECT shardid INTO selected_shard FROM pg_dist_shard WHERE logicalrelid='test_2pcskip'::regclass LIMIT 1; +SELECT shardid INTO selected_shard +FROM citus_shards +WHERE table_name='test_2pcskip'::regclass AND nodeport = :worker_1_port +LIMIT 1; SELECT COUNT(*) FROM pg_dist_transaction; count --------------------------------------------------------------------- diff --git a/src/test/regress/expected/multi_utilities.out b/src/test/regress/expected/multi_utilities.out index 5849eb52f..c5a0be4c8 100644 --- a/src/test/regress/expected/multi_utilities.out +++ b/src/test/regress/expected/multi_utilities.out @@ -4,6 +4,8 @@ SET citus.next_shard_id TO 990000; -- =================================================================== SET citus.shard_count TO 2; SET citus.shard_replication_factor TO 1; +CREATE SCHEMA multi_utilities; +SET search_path TO multi_utilities, public; CREATE TABLE sharded_table ( name text, id bigint ); SELECT create_distributed_table('sharded_table', 'id', 'hash'); create_distributed_table @@ -196,22 +198,26 @@ SELECT master_create_worker_shards('second_dustbunnies', 1, 2); -- run VACUUM and ANALYZE against the table on the master \c - - :master_host :master_port +SET search_path TO multi_utilities, public; VACUUM dustbunnies; ANALYZE dustbunnies; -- send a VACUUM FULL and a VACUUM ANALYZE VACUUM (FULL) dustbunnies; VACUUM ANALYZE dustbunnies; \c - - :public_worker_1_host :worker_1_port +SET search_path TO multi_utilities, public; -- disable auto-VACUUM for next test ALTER TABLE dustbunnies_990002 SET (autovacuum_enabled = false); SELECT relfrozenxid AS frozenxid FROM pg_class WHERE oid='dustbunnies_990002'::regclass \gset -- send a VACUUM FREEZE after adding a new row \c - - :master_host :master_port +SET search_path TO multi_utilities, public; INSERT INTO dustbunnies VALUES (5, 'peter'); VACUUM (FREEZE) dustbunnies; -- verify that relfrozenxid increased \c - - :public_worker_1_host :worker_1_port +SET search_path TO multi_utilities, public; SELECT relfrozenxid::text::integer > :frozenxid AS frozen_performed FROM pg_class WHERE oid='dustbunnies_990002'::regclass; frozen_performed @@ -231,10 +237,12 @@ WHERE tablename = 'dustbunnies_990002' ORDER BY attname; -- add NULL values, then perform column-specific ANALYZE \c - - :master_host :master_port +SET search_path TO multi_utilities, public; INSERT INTO dustbunnies VALUES (6, NULL, NULL); ANALYZE dustbunnies (name); -- verify that name's NULL ratio is updated but age's is not \c - - :public_worker_1_host :worker_1_port +SET search_path TO multi_utilities, public; SELECT attname, null_frac FROM pg_stats WHERE tablename = 'dustbunnies_990002' ORDER BY attname; attname | null_frac @@ -245,16 +253,17 @@ WHERE tablename = 'dustbunnies_990002' ORDER BY attname; (3 rows) \c - - :master_host :master_port +SET search_path TO multi_utilities, public; SET citus.log_remote_commands TO ON; -- check for multiple table vacuum VACUUM dustbunnies, second_dustbunnies; -NOTICE: issuing VACUUM public.dustbunnies_990002 +NOTICE: issuing VACUUM multi_utilities.dustbunnies_990002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM public.dustbunnies_990002 +NOTICE: issuing VACUUM multi_utilities.dustbunnies_990002 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM public.second_dustbunnies_990003 +NOTICE: issuing VACUUM multi_utilities.second_dustbunnies_990003 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM public.second_dustbunnies_990003 +NOTICE: issuing VACUUM multi_utilities.second_dustbunnies_990003 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- and do not propagate when using targeted VACUUM without DDL propagation SET citus.enable_ddl_propagation to false; @@ -344,43 +353,45 @@ DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx insert into local_vacuum_table select i from generate_series(1,1000000) i; delete from local_vacuum_table; VACUUM local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); - pg_size_pretty +SELECT CASE WHEN s BETWEEN 20000000 AND 25000000 THEN 22500000 ELSE s END +FROM pg_total_relation_size('local_vacuum_table') s ; + s --------------------------------------------------------------------- - 21 MB + 22500000 (1 row) -- vacuum full deallocates pages of dead tuples whereas normal vacuum only marks dead tuples on visibility map VACUUM FULL local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); - pg_size_pretty +SELECT CASE WHEN s BETWEEN 0 AND 50000 THEN 25000 ELSE s END size +FROM pg_total_relation_size('local_vacuum_table') s ; + size --------------------------------------------------------------------- - 16 kB + 25000 (1 row) -- should propagate to all workers because table is reference table VACUUM reference_vacuum_table; -NOTICE: issuing VACUUM public.reference_vacuum_table_970000 +NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM public.reference_vacuum_table_970000 +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 public.distributed_vacuum_table_970001 +NOTICE: issuing VACUUM multi_utilities.distributed_vacuum_table_970001 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- only distributed_vacuum_table and reference_vacuum_table should propagate VACUUM distributed_vacuum_table, local_vacuum_table, reference_vacuum_table; -NOTICE: issuing VACUUM public.distributed_vacuum_table_970001 +NOTICE: issuing VACUUM multi_utilities.distributed_vacuum_table_970001 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM public.reference_vacuum_table_970000 +NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM public.reference_vacuum_table_970000 +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 public.reference_vacuum_table_970000 +NOTICE: issuing VACUUM multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM public.reference_vacuum_table_970000 +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; @@ -389,19 +400,21 @@ VACUUM (DISABLE_PAGE_SKIPPING false) local_vacuum_table; insert into local_vacuum_table select i from generate_series(1,1000000) i; delete from local_vacuum_table; VACUUM (INDEX_CLEANUP OFF, PARALLEL 1) local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); - pg_size_pretty +SELECT CASE WHEN s BETWEEN 50000000 AND 70000000 THEN 60000000 ELSE s END size +FROM pg_total_relation_size('local_vacuum_table') s ; + size --------------------------------------------------------------------- - 56 MB + 60000000 (1 row) insert into local_vacuum_table select i from generate_series(1,1000000) i; delete from local_vacuum_table; VACUUM (INDEX_CLEANUP ON, PARALLEL 1) local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); - pg_size_pretty +SELECT CASE WHEN s BETWEEN 20000000 AND 49999999 THEN 35000000 ELSE s END size +FROM pg_total_relation_size('local_vacuum_table') s ; + size --------------------------------------------------------------------- - 21 MB + 35000000 (1 row) -- vacuum (truncate false) should not attempt to truncate off any empty pages at the end of the table (default is true) @@ -428,9 +441,9 @@ select analyze_count from pg_stat_all_tables where relname = 'local_vacuum_table (2 rows) vacuum (analyze) local_vacuum_table, reference_vacuum_table; -NOTICE: issuing VACUUM (ANALYZE) public.reference_vacuum_table_970000 +NOTICE: issuing VACUUM (ANALYZE) multi_utilities.reference_vacuum_table_970000 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing VACUUM (ANALYZE) public.reference_vacuum_table_970000 +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); @@ -476,93 +489,38 @@ SELECT create_distributed_table ('dist', 'a'); (1 row) SET citus.log_remote_commands TO ON; +SET citus.grep_remote_commands = '%ANALYZE%'; -- should propagate to all workers because no table is specified ANALYZE; -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 BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); -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 SET citus.enable_ddl_propagation TO 'off' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing ANALYZE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx NOTICE: issuing ANALYZE DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SET citus.enable_ddl_propagation TO 'on' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing SET citus.enable_ddl_propagation TO 'on' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- should not propagate because no distributed table is specified ANALYZE local_analyze_table; -- should propagate to all workers because table is reference table ANALYZE reference_analyze_table; -NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); +NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 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 ANALYZE public.reference_analyze_table_970002 -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing ANALYZE public.reference_analyze_table_970002 -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' +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 public.distributed_analyze_table_970003 +NOTICE: issuing ANALYZE multi_utilities.distributed_analyze_table_970003 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -- only distributed_analyze_table and reference_analyze_table should propagate ANALYZE distributed_analyze_table, local_analyze_table, reference_analyze_table; -NOTICE: issuing ANALYZE public.distributed_analyze_table_970003 +NOTICE: issuing ANALYZE multi_utilities.distributed_analyze_table_970003 DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); +NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 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 ANALYZE public.reference_analyze_table_970002 -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing ANALYZE public.reference_analyze_table_970002 -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' +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 BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;SELECT assign_distributed_transaction_id(xx, xx, 'xxxxxxx'); +NOTICE: issuing ANALYZE multi_utilities.reference_analyze_table_970002 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 ANALYZE public.reference_analyze_table_970002 -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing ANALYZE public.reference_analyze_table_970002 -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing PREPARE TRANSACTION 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' -DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx -NOTICE: issuing COMMIT PREPARED 'citus_xx_xx_xx_xx' +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; @@ -570,5 +528,9 @@ ANALYZE distributed_analyze_table; SET citus.enable_ddl_propagation TO ON; -- analyze only specified columns for corresponding tables ANALYZE loc(b), dist(a); -NOTICE: issuing ANALYZE public.dist_970004 (a) +NOTICE: issuing ANALYZE multi_utilities.dist_970004 (a) DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx +RESET citus.log_remote_commands; +RESET citus.grep_remote_commands; +SET client_min_messages TO WARNING; +DROP SCHEMA multi_utilities CASCADE; diff --git a/src/test/regress/expected/multi_view.out b/src/test/regress/expected/multi_view.out index c7a6b44e0..70fa10874 100644 --- a/src/test/regress/expected/multi_view.out +++ b/src/test/regress/expected/multi_view.out @@ -785,6 +785,7 @@ EXPLAIN (COSTS FALSE) SELECT user_id FROM recent_selected_users GROUP BY 1 ORDER Filter: ((value_1 >= 1) AND (value_1 < 3)) (19 rows) +SELECT public.coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT * FROM ( (SELECT user_id FROM recent_users) @@ -792,32 +793,14 @@ EXPLAIN (COSTS FALSE) SELECT * (SELECT user_id FROM selected_users) ) u WHERE user_id < 4 AND user_id > 1 ORDER BY user_id; - QUERY PLAN +$Q$); + coordinator_plan --------------------------------------------------------------------- Sort Sort Key: remote_scan.user_id -> Custom Scan (Citus Adaptive) Task Count: 4 - Tasks Shown: One of 4 - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Unique - -> Sort - Sort Key: recent_users.user_id - -> Append - -> Subquery Scan on recent_users - -> Sort - Sort Key: (max(users_table."time")) DESC - -> GroupAggregate - Group Key: users_table.user_id - Filter: (max(users_table."time") > '2017-11-23 16:20:33.264457'::timestamp without time zone) - -> Sort - Sort Key: users_table.user_id - -> Seq Scan on users_table_1400256 users_table - Filter: ((user_id < 4) AND (user_id > 1)) - -> Seq Scan on users_table_1400256 users_table_1 - Filter: ((value_1 >= 1) AND (value_1 < 3) AND (user_id < 4) AND (user_id > 1)) -(23 rows) +(4 rows) EXPLAIN (COSTS FALSE) SELECT et.* FROM recent_10_users JOIN events_table et USING(user_id) ORDER BY et.time DESC LIMIT 10; QUERY PLAN diff --git a/src/test/regress/expected/mx_coordinator_shouldhaveshards.out b/src/test/regress/expected/mx_coordinator_shouldhaveshards.out index cbfe25281..438e1dcdd 100644 --- a/src/test/regress/expected/mx_coordinator_shouldhaveshards.out +++ b/src/test/regress/expected/mx_coordinator_shouldhaveshards.out @@ -1,3 +1,17 @@ +-- +-- MX_COORDINATOR_SHOULDHAVESHARDS +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA mx_coordinator_shouldhaveshards; SET search_path TO mx_coordinator_shouldhaveshards; SET citus.shard_replication_factor to 1; @@ -99,7 +113,7 @@ inserts AS ( RETURNING * ) 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: 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: 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 @@ -160,7 +174,7 @@ inserts AS ( RETURNING * ) 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: 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: 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 @@ -225,7 +239,7 @@ inserts AS ( RETURNING * ) 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: 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: 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 @@ -286,7 +300,7 @@ inserts AS ( RETURNING * ) 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: 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: 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 diff --git a/src/test/regress/expected/mx_coordinator_shouldhaveshards_0.out b/src/test/regress/expected/mx_coordinator_shouldhaveshards_0.out new file mode 100644 index 000000000..398229fbb --- /dev/null +++ b/src/test/regress/expected/mx_coordinator_shouldhaveshards_0.out @@ -0,0 +1,331 @@ +-- +-- MX_COORDINATOR_SHOULDHAVESHARDS +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA mx_coordinator_shouldhaveshards; +SET search_path TO mx_coordinator_shouldhaveshards; +SET citus.shard_replication_factor to 1; +SET client_min_messages TO WARNING; +SELECT 1 FROM master_add_node('localhost', :master_port, groupid => 0); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +RESET client_min_messages; +SELECT 1 FROM master_set_node_property('localhost', :master_port, 'shouldhaveshards', true); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- issue 4508 table_1 and table_2 are used to test some edge cases +-- around intermediate result pruning +CREATE TABLE table_1 (key int, value text); +SELECT create_distributed_table('table_1', 'key', colocate_with := 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE table_2 (key int, value text); +SELECT create_distributed_table('table_2', 'key', colocate_with := 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO table_1 VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'); +INSERT INTO table_2 VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'), (6, '6'); +SET citus.shard_replication_factor to 2; +CREATE TABLE table_1_rep (key int, value text); +SELECT create_distributed_table('table_1_rep', 'key', colocate_with := 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE table_2_rep (key int, value text); +SELECT create_distributed_table('table_2_rep', 'key', colocate_with := 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO table_1_rep VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'); +INSERT INTO table_2_rep VALUES (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'), (6, '6'); +set citus.log_intermediate_results TO ON; +set client_min_messages to debug1; +WITH a AS (SELECT * FROM table_1 ORDER BY 1,2 DESC LIMIT 1) +SELECT count(*), +key +FROM a JOIN table_2 USING (key) +GROUP BY key +HAVING (max(table_2.value) >= (SELECT value FROM a)); +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: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, a.key 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)) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count | key +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +WITH a AS (SELECT * FROM table_1 ORDER BY 1,2 DESC LIMIT 1) +INSERT INTO table_1 SELECT count(*), +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: 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)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT auto_coerced_by_citus_0 AS key, auto_coerced_by_citus_1 AS value FROM (SELECT intermediate_result.auto_coerced_by_citus_0, intermediate_result.auto_coerced_by_citus_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(auto_coerced_by_citus_0 integer, auto_coerced_by_citus_1 text)) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +WITH stats AS ( + SELECT count(key) m FROM table_1 +), +inserts AS ( + INSERT INTO table_2 + SELECT key, count(*) + FROM table_1 + WHERE key >= (SELECT m FROM stats) + GROUP BY key + HAVING count(*) <= (SELECT m FROM stats) + LIMIT 1 + RETURNING * +) 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: 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 +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Collecting INSERT ... SELECT results on coordinator + count +--------------------------------------------------------------------- + 0 +(1 row) + +WITH a AS (SELECT * FROM table_1_rep ORDER BY 1,2 DESC LIMIT 1) +SELECT count(*), +key +FROM a JOIN table_2_rep USING (key) +GROUP BY key +HAVING (max(table_2_rep.value) >= (SELECT value FROM a)); +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: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, a.key 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)) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count | key +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +WITH a AS (SELECT * FROM table_1_rep ORDER BY 1,2 DESC LIMIT 1) +INSERT INTO table_1_rep SELECT count(*), +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: 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)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT auto_coerced_by_citus_0 AS key, auto_coerced_by_citus_1 AS value FROM (SELECT intermediate_result.auto_coerced_by_citus_0, intermediate_result.auto_coerced_by_citus_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(auto_coerced_by_citus_0 integer, auto_coerced_by_citus_1 text)) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +WITH stats AS ( + SELECT count(key) m FROM table_1_rep +), +inserts AS ( + INSERT INTO table_2_rep + SELECT key, count(*) + FROM table_1_rep + WHERE key >= (SELECT m FROM stats) + GROUP BY key + HAVING count(*) <= (SELECT m FROM stats) + LIMIT 1 + RETURNING * +) 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: 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 +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Collecting INSERT ... SELECT results on coordinator + count +--------------------------------------------------------------------- + 0 +(1 row) + +\c - - - :worker_1_port +SET search_path TO mx_coordinator_shouldhaveshards; +set citus.log_intermediate_results TO ON; +set client_min_messages to debug1; +WITH a AS (SELECT * FROM table_1 ORDER BY 1,2 DESC LIMIT 1) +SELECT count(*), +key +FROM a JOIN table_2 USING (key) +GROUP BY key +HAVING (max(table_2.value) >= (SELECT value FROM a)); +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: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, a.key 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)) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count | key +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +WITH a AS (SELECT * FROM table_1 ORDER BY 1,2 DESC LIMIT 1) +INSERT INTO table_1 SELECT count(*), +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: 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)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT auto_coerced_by_citus_0 AS key, auto_coerced_by_citus_1 AS value FROM (SELECT intermediate_result.auto_coerced_by_citus_0, intermediate_result.auto_coerced_by_citus_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(auto_coerced_by_citus_0 integer, auto_coerced_by_citus_1 text)) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +WITH stats AS ( + SELECT count(key) m FROM table_1 +), +inserts AS ( + INSERT INTO table_2 + SELECT key, count(*) + FROM table_1 + WHERE key >= (SELECT m FROM stats) + GROUP BY key + HAVING count(*) <= (SELECT m FROM stats) + LIMIT 1 + RETURNING * +) 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: 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 +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Collecting INSERT ... SELECT results on coordinator + count +--------------------------------------------------------------------- + 0 +(1 row) + +WITH a AS (SELECT * FROM table_1_rep ORDER BY 1,2 DESC LIMIT 1) +SELECT count(*), +key +FROM a JOIN table_2_rep USING (key) +GROUP BY key +HAVING (max(table_2_rep.value) >= (SELECT value FROM a)); +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: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count, a.key 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)) +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx + count | key +--------------------------------------------------------------------- + 1 | 1 +(1 row) + +WITH a AS (SELECT * FROM table_1_rep ORDER BY 1,2 DESC LIMIT 1) +INSERT INTO table_1_rep SELECT count(*), +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: 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)) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT auto_coerced_by_citus_0 AS key, auto_coerced_by_citus_1 AS value FROM (SELECT intermediate_result.auto_coerced_by_citus_0, intermediate_result.auto_coerced_by_citus_1 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(auto_coerced_by_citus_0 integer, auto_coerced_by_citus_1 text)) citus_insert_select_subquery +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Subplan XXX_1 will be written to local file +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +WITH stats AS ( + SELECT count(key) m FROM table_1_rep +), +inserts AS ( + INSERT INTO table_2_rep + SELECT key, count(*) + FROM table_1_rep + WHERE key >= (SELECT m FROM stats) + GROUP BY key + HAVING count(*) <= (SELECT m FROM stats) + LIMIT 1 + RETURNING * +) 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: 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 +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_1 will be sent to localhost:xxxxx +DEBUG: Subplan XXX_2 will be written to local file +DEBUG: Collecting INSERT ... SELECT results on coordinator + count +--------------------------------------------------------------------- + 0 +(1 row) + +\c - - - :master_port +SELECT 1 FROM master_set_node_property('localhost', :master_port, 'shouldhaveshards', false); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SET client_min_messages TO ERROR; +DROP SCHEMA mx_coordinator_shouldhaveshards CASCADE; +SELECT master_remove_node('localhost', :master_port); + master_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/pg12.out b/src/test/regress/expected/pg12.out index c5e4a11c7..8aee343e2 100644 --- a/src/test/regress/expected/pg12.out +++ b/src/test/regress/expected/pg12.out @@ -588,8 +588,12 @@ NOTICE: renaming the new table to test_pg12.generated_stored_ref ROLLBACK; BEGIN; -- drop some of the columns not having "generated always as stored" expressions - -- this would drop generated columns too - ALTER TABLE generated_stored_ref DROP COLUMN col_1; + -- PRE PG15, this would drop generated columns too + -- In PG15, CASCADE option must be specified + -- Relevant PG Commit: cb02fcb4c95bae08adaca1202c2081cfc81a28b5 + SET client_min_messages TO WARNING; + ALTER TABLE generated_stored_ref DROP COLUMN col_1 CASCADE; + RESET client_min_messages; ALTER TABLE generated_stored_ref DROP COLUMN col_4; -- show that undistribute_table works fine SELECT undistribute_table('generated_stored_ref'); diff --git a/src/test/regress/expected/pg14.out b/src/test/regress/expected/pg14.out index fa6e42065..4980e3ca2 100644 --- a/src/test/regress/expected/pg14.out +++ b/src/test/regress/expected/pg14.out @@ -1,7 +1,7 @@ SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int > 13 AS server_version_above_thirteen +SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 \gset -\if :server_version_above_thirteen +\if :server_version_ge_14 \else \q \endif @@ -279,7 +279,7 @@ SELECT create_distributed_table('col_compression', 'a', shard_count:=4); (1 row) -SELECT attname || ' ' || attcompression AS column_compression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'col\_compression%' AND attnum > 0 ORDER BY 1; +SELECT attname || ' ' || attcompression::text AS column_compression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'col\_compression%' AND attnum > 0 ORDER BY 1; column_compression --------------------------------------------------------------------- a p @@ -287,7 +287,7 @@ SELECT attname || ' ' || attcompression AS column_compression FROM pg_attribute (2 rows) SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); column_compression --------------------------------------------------------------------- @@ -313,7 +313,7 @@ NOTICE: Moving shard xxxxx from localhost:xxxxx to localhost:xxxxx ... CALL citus_cleanup_orphaned_shards(); NOTICE: cleaned up 1 orphaned shards SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); column_compression --------------------------------------------------------------------- @@ -325,7 +325,7 @@ SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regcla ALTER TABLE col_compression ALTER COLUMN b SET COMPRESSION pglz; ALTER TABLE col_compression ALTER COLUMN a SET COMPRESSION default; SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); column_compression --------------------------------------------------------------------- @@ -336,7 +336,7 @@ SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regcla -- test propagation of ALTER TABLE .. ADD COLUMN .. COMPRESSION .. ALTER TABLE col_compression ADD COLUMN c TEXT COMPRESSION pglz; SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); column_compression --------------------------------------------------------------------- @@ -354,7 +354,7 @@ SELECT create_distributed_table('col_comp_par', 'a'); CREATE TABLE col_comp_par_1 PARTITION OF col_comp_par FOR VALUES FROM ('abc') TO ('def'); SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_comp\_par\_1\_%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_comp\_par\_1\_%' AND attnum > 0 ORDER BY 1 )$$); column_compression --------------------------------------------------------------------- @@ -1467,6 +1467,34 @@ SELECT create_distributed_table('compression_and_generated_col', 'rev', colocate (1 row) +-- See that it's enabling the binary option for logical replication +BEGIN; +SET LOCAL citus.log_remote_commands TO ON; +SET LOCAL citus.grep_remote_commands = '%CREATE SUBSCRIPTION%'; +SELECT citus_move_shard_placement(980042, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode := 'force_logical'); +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx, binary=true) +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx + citus_move_shard_placement +--------------------------------------------------------------------- + +(1 row) + +ROLLBACK; +-- See that it doesn't enable the binary option for logical replication if we +-- disable binary protocol. +BEGIN; +SET LOCAL citus.log_remote_commands TO ON; +SET LOCAL citus.grep_remote_commands = '%CREATE SUBSCRIPTION%'; +SET LOCAL citus.enable_binary_protocol = FALSE; +SELECT citus_move_shard_placement(980042, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode := 'force_logical'); +NOTICE: issuing CREATE SUBSCRIPTION citus_shard_move_subscription_xxxxxxx CONNECTION 'host=''localhost'' port=xxxxx user=''postgres'' dbname=''regression'' connect_timeout=20' PUBLICATION citus_shard_move_publication_xxxxxxx_xxxxxxx WITH (citus_use_authinfo=true, create_slot=false, copy_data=false, enabled=false, slot_name=citus_shard_move_slot_xxxxxxx_xxxxxxx) +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx + citus_move_shard_placement +--------------------------------------------------------------------- + +(1 row) + +ROLLBACK; DROP TABLE compression_and_defaults, compression_and_generated_col; -- cleanup set client_min_messages to error; diff --git a/src/test/regress/expected/pg14_0.out b/src/test/regress/expected/pg14_0.out index 65b2376bc..cff095489 100644 --- a/src/test/regress/expected/pg14_0.out +++ b/src/test/regress/expected/pg14_0.out @@ -1,6 +1,6 @@ SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int > 13 AS server_version_above_thirteen +SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 \gset -\if :server_version_above_thirteen +\if :server_version_ge_14 \else \q diff --git a/src/test/regress/expected/pg15.out b/src/test/regress/expected/pg15.out new file mode 100644 index 000000000..f59586b7a --- /dev/null +++ b/src/test/regress/expected/pg15.out @@ -0,0 +1,402 @@ +-- +-- PG15 +-- +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 +CREATE SCHEMA pg15; +SET search_path TO pg15; +SET citus.next_shard_id TO 960000; +SET citus.shard_count TO 4; +-- +-- In PG15, there is an added option to use ICU as global locale provider. +-- pg_collation has three locale-related fields: collcollate and collctype, +-- which are libc-related fields, and a new one colliculocale, which is the +-- ICU-related field. Only the libc-related fields or the ICU-related field +-- is set, never both. +-- Relevant PG commits: +-- f2553d43060edb210b36c63187d52a632448e1d2 +-- 54637508f87bd5f07fb9406bac6b08240283be3b +-- +-- fail, needs "locale" +CREATE COLLATION german_phonebook_test (provider = icu, lc_collate = 'de-u-co-phonebk'); +ERROR: parameter "locale" must be specified +-- fail, needs "locale" +CREATE COLLATION german_phonebook_test (provider = icu, lc_collate = 'de-u-co-phonebk', lc_ctype = 'de-u-co-phonebk'); +ERROR: parameter "locale" must be specified +-- works +CREATE COLLATION german_phonebook_test (provider = icu, locale = 'de-u-co-phonebk'); +-- with icu provider, colliculocale will be set, collcollate and collctype will be null +SELECT result FROM run_command_on_all_nodes(' + SELECT collcollate FROM pg_collation WHERE collname = ''german_phonebook_test''; +'); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT result FROM run_command_on_all_nodes(' + SELECT collctype FROM pg_collation WHERE collname = ''german_phonebook_test''; +'); + result +--------------------------------------------------------------------- + + + +(3 rows) + +SELECT result FROM run_command_on_all_nodes(' + SELECT colliculocale FROM pg_collation WHERE collname = ''german_phonebook_test''; +'); + result +--------------------------------------------------------------------- + de-u-co-phonebk + de-u-co-phonebk + de-u-co-phonebk +(3 rows) + +-- with non-icu provider, colliculocale will be null, collcollate and collctype will be set +CREATE COLLATION default_provider (provider = libc, lc_collate = "POSIX", lc_ctype = "POSIX"); +SELECT result FROM run_command_on_all_nodes(' + SELECT collcollate FROM pg_collation WHERE collname = ''default_provider''; +'); + result +--------------------------------------------------------------------- + POSIX + POSIX + POSIX +(3 rows) + +SELECT result FROM run_command_on_all_nodes(' + SELECT collctype FROM pg_collation WHERE collname = ''default_provider''; +'); + result +--------------------------------------------------------------------- + POSIX + POSIX + POSIX +(3 rows) + +SELECT result FROM run_command_on_all_nodes(' + SELECT colliculocale FROM pg_collation WHERE collname = ''default_provider''; +'); + result +--------------------------------------------------------------------- + + + +(3 rows) + +-- +-- In PG15, Renaming triggers on partitioned tables had two problems +-- recurses to renaming the triggers on the partitions as well. +-- Here we test that distributed triggers behave the same way. +-- Relevant PG commit: +-- 80ba4bb383538a2ee846fece6a7b8da9518b6866 +-- +SET citus.enable_unsafe_triggers TO true; +CREATE TABLE sale( + sale_date date not null, + state_code text, + product_sku text, + units integer) + PARTITION BY list (state_code); +ALTER TABLE sale ADD CONSTRAINT sale_pk PRIMARY KEY (state_code, sale_date); +CREATE TABLE sale_newyork PARTITION OF sale FOR VALUES IN ('NY'); +CREATE TABLE sale_california PARTITION OF sale FOR VALUES IN ('CA'); +CREATE TABLE record_sale( + operation_type text not null, + product_sku text, + state_code text, + units integer, + PRIMARY KEY(state_code, product_sku, operation_type, units)); +SELECT create_distributed_table('sale', 'state_code'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('record_sale', 'state_code', colocate_with := 'sale'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION record_sale() +RETURNS trigger +AS $$ +BEGIN + INSERT INTO pg15.record_sale(operation_type, product_sku, state_code, units) + VALUES (TG_OP, NEW.product_sku, NEW.state_code, NEW.units); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER record_sale_trigger +AFTER INSERT OR UPDATE OR DELETE ON sale +FOR EACH ROW EXECUTE FUNCTION pg15.record_sale(); +CREATE VIEW sale_triggers AS + SELECT tgname, tgrelid::regclass, tgenabled + FROM pg_trigger + WHERE tgrelid::regclass::text like 'sale%' + ORDER BY 1, 2; +SELECT * FROM sale_triggers ORDER BY 1, 2; + tgname | tgrelid | tgenabled +--------------------------------------------------------------------- + record_sale_trigger | sale | O + record_sale_trigger | sale_newyork | O + record_sale_trigger | sale_california | O + truncate_trigger_xxxxxxx | sale | O + truncate_trigger_xxxxxxx | sale_california | O + truncate_trigger_xxxxxxx | sale_newyork | O +(6 rows) + +ALTER TRIGGER "record_sale_trigger" ON "pg15"."sale" RENAME TO "new_record_sale_trigger"; +SELECT * FROM sale_triggers ORDER BY 1, 2; + tgname | tgrelid | tgenabled +--------------------------------------------------------------------- + new_record_sale_trigger | sale | O + new_record_sale_trigger | sale_newyork | O + new_record_sale_trigger | sale_california | O + truncate_trigger_xxxxxxx | sale | O + truncate_trigger_xxxxxxx | sale_california | O + truncate_trigger_xxxxxxx | sale_newyork | O +(6 rows) + +-- +-- In PG15, For GENERATED columns, all dependencies of the generation +-- expression are recorded as NORMAL dependencies of the column itself. +-- This requires CASCADE to drop generated cols with the original col. +-- Test this behavior in distributed table, specifically with +-- undistribute_table within a transaction. +-- Relevant PG Commit: cb02fcb4c95bae08adaca1202c2081cfc81a28b5 +-- +CREATE TABLE generated_stored_ref ( + col_1 int, + col_2 int, + col_3 int generated always as (col_1+col_2) stored, + col_4 int, + col_5 int generated always as (col_4*2-col_1) stored +); +SELECT create_reference_table ('generated_stored_ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +-- populate the table +INSERT INTO generated_stored_ref (col_1, col_4) VALUES (1,2), (11,12); +INSERT INTO generated_stored_ref (col_1, col_2, col_4) VALUES (100,101,102), (200,201,202); +SELECT * FROM generated_stored_ref ORDER BY 1,2,3,4,5; + col_1 | col_2 | col_3 | col_4 | col_5 +--------------------------------------------------------------------- + 1 | | | 2 | 3 + 11 | | | 12 | 13 + 100 | 101 | 201 | 102 | 104 + 200 | 201 | 401 | 202 | 204 +(4 rows) + +-- fails, CASCADE must be specified +-- will test CASCADE inside the transcation +ALTER TABLE generated_stored_ref DROP COLUMN col_1; +ERROR: cannot drop column col_1 of table generated_stored_ref because other objects depend on it +DETAIL: column col_3 of table generated_stored_ref depends on column col_1 of table generated_stored_ref +column col_5 of table generated_stored_ref depends on column col_1 of table generated_stored_ref +HINT: Use DROP ... CASCADE to drop the dependent objects too. +BEGIN; + -- drops col_1, col_3, col_5 + ALTER TABLE generated_stored_ref DROP COLUMN col_1 CASCADE; +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 + ALTER TABLE generated_stored_ref DROP COLUMN col_4; + -- show that undistribute_table works fine + SELECT undistribute_table('generated_stored_ref'); +NOTICE: creating a new table for pg15.generated_stored_ref +NOTICE: moving the data of pg15.generated_stored_ref +NOTICE: dropping the old pg15.generated_stored_ref +NOTICE: renaming the new table to pg15.generated_stored_ref + undistribute_table +--------------------------------------------------------------------- + +(1 row) + + INSERT INTO generated_stored_ref VALUES (5); + SELECT * FROM generated_stored_REF ORDER BY 1; + col_2 +--------------------------------------------------------------------- + 5 + 101 + 201 + + +(5 rows) + +ROLLBACK; +SELECT undistribute_table('generated_stored_ref'); +NOTICE: creating a new table for pg15.generated_stored_ref +NOTICE: moving the data of pg15.generated_stored_ref +NOTICE: dropping the old pg15.generated_stored_ref +NOTICE: renaming the new table to pg15.generated_stored_ref + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +-- +-- In PG15, there is a new command called MERGE +-- It is currently not supported for Citus tables +-- Test the behavior with various commands with Citus table types +-- Relevant PG Commit: 7103ebb7aae8ab8076b7e85f335ceb8fe799097c +-- +CREATE TABLE tbl1 +( + x INT +); +CREATE TABLE tbl2 +( + x INT +); +-- 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 +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; +ERROR: MERGE command is not supported on Citus tables yet +SELECT undistribute_table('tbl1'); +NOTICE: creating a new table for pg15.tbl1 +NOTICE: moving the data of pg15.tbl1 +NOTICE: dropping the old pg15.tbl1 +NOTICE: renaming the new table to pg15.tbl1 + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +-- the other table is Citus local table, fails +SELECT citus_add_local_table_to_metadata('tbl2'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; +ERROR: MERGE command is not supported on Citus tables yet +-- one table is reference, the other local, not supported +SELECT create_reference_table('tbl2'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; +ERROR: MERGE command is not supported on Citus tables yet +-- now, both are reference, still not supported +SELECT create_reference_table('tbl1'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; +ERROR: MERGE command is not supported on Citus tables yet +-- now, both distributed, not works +SELECT undistribute_table('tbl1'); +NOTICE: creating a new table for pg15.tbl1 +NOTICE: moving the data of pg15.tbl1 +NOTICE: dropping the old pg15.tbl1 +NOTICE: renaming the new table to pg15.tbl1 + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT undistribute_table('tbl2'); +NOTICE: creating a new table for pg15.tbl2 +NOTICE: moving the data of pg15.tbl2 +NOTICE: dropping the old pg15.tbl2 +NOTICE: renaming the new table to pg15.tbl2 + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT 1 FROM citus_remove_node('localhost', :master_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT create_distributed_table('tbl1', 'x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('tbl2', 'x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; +ERROR: MERGE command is not supported on Citus tables yet +-- also, not inside subqueries & ctes +WITH targq AS ( + SELECT * FROM tbl2 +) +MERGE INTO tbl1 USING targq ON (true) +WHEN MATCHED THEN DELETE; +ERROR: MERGE command is not supported on Citus tables yet +-- crashes on beta3, fixed on 15 stable +--WITH foo AS ( +-- MERGE INTO tbl1 USING tbl2 ON (true) +-- WHEN MATCHED THEN DELETE +--) SELECT * FROM foo; +--COPY ( +-- MERGE INTO tbl1 USING tbl2 ON (true) +-- WHEN MATCHED THEN DELETE +--) TO stdout; +MERGE INTO tbl1 t +USING tbl2 +ON (true) +WHEN MATCHED THEN + UPDATE SET x = (SELECT count(*) FROM tbl2); +ERROR: MERGE command is not supported on Citus tables yet +-- Clean up +DROP SCHEMA pg15 CASCADE; +NOTICE: drop cascades to 9 other objects +DETAIL: drop cascades to collation german_phonebook_test +drop cascades to collation default_provider +drop cascades to table sale +drop cascades to table record_sale +drop cascades to function record_sale() +drop cascades to view sale_triggers +drop cascades to table generated_stored_ref +drop cascades to table tbl1 +drop cascades to table tbl2 diff --git a/src/test/regress/expected/pg15_0.out b/src/test/regress/expected/pg15_0.out new file mode 100644 index 000000000..b1ed9cc5b --- /dev/null +++ b/src/test/regress/expected/pg15_0.out @@ -0,0 +1,9 @@ +-- +-- PG15 +-- +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/pg15_json.out b/src/test/regress/expected/pg15_json.out new file mode 100644 index 000000000..be263337a --- /dev/null +++ b/src/test/regress/expected/pg15_json.out @@ -0,0 +1,488 @@ +-- +-- PG15+ test +-- +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 +CREATE SCHEMA pg15_json; +SET search_path TO pg15_json; +SET citus.next_shard_id TO 1687000; +CREATE TABLE test_table(id bigserial, value text); +SELECT create_distributed_table('test_table', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO test_table (value) SELECT i::text FROM generate_series(0,100)i; +CREATE TABLE my_films(id bigserial, js jsonb); +SELECT create_distributed_table('my_films', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas", "director" : "Woody Allen"}, + { "title" : "The Dinner Game", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo", "director" : "Akira Kurosawa" } ] } + ] }'); +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas2", "director" : "Woody Allen"}, + { "title" : "The Dinner Game2", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo2", "director" : "Akira Kurosawa" } ] } + ] }'); +-- a router query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + WHERE my_films.id = 1 + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa +(5 rows) + +-- router query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) as sub +WHERE my_films.id = 1 ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa +(5 rows) + +-- router query with an explicit LATEREL SUBQUERY and LIMIT +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt ORDER BY id DESC LIMIT 1) as sub +WHERE my_films.id = 1 ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 4 | drama | Yojimbo | Akira Kurosawa +(1 row) + +-- set it DEBUG1 in case the plan changes +-- we can see details +SET client_min_messages TO DEBUG1; +-- a mult-shard query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- recursively plan subqueries that has JSON_TABLE +SELECT count(*) FROM +( + SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + LIMIT 1) as sub_with_json, test_table +WHERE test_table.id = sub_with_json.id; +DEBUG: push down of limit count: 1 +DEBUG: generating subplan XXX_1 for subquery SELECT jt.id, jt.kind, jt.title, jt.director FROM pg15_json.my_films, LATERAL JSON_TABLE(my_films.js, '$."favorites"[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY, kind text PATH '$."kind"', NESTED PATH '$."films"[*]' AS json_table_path_2 COLUMNS (title text PATH '$."title"', director text PATH '$."director"')) PLAN (json_table_path_1 OUTER json_table_path_2)) jt LIMIT 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.id, intermediate_result.kind, intermediate_result.title, intermediate_result.director FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, kind text, title text, director text)) sub_with_json, pg15_json.test_table WHERE (test_table.id OPERATOR(pg_catalog.=) sub_with_json.id) + count +--------------------------------------------------------------------- + 1 +(1 row) + +-- multi-shard query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- JSON_TABLE can be on the inner part of an outer joion +SELECT sub.* +FROM my_films LEFT JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- we can pushdown this correlated subquery in WHERE clause +SELECT count(*) +FROM my_films WHERE + (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000); + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- we can pushdown this correlated subquery in SELECT clause + SELECT (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) +FROM my_films; + ?column? +--------------------------------------------------------------------- + t + t +(2 rows) + +-- multi-shard query with an explicit LATEREL SUBQUERY +-- along with other tables +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- non-colocated join fails +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id != test_table.id) + ORDER BY 1,2,3,4; +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +-- JSON_TABLE can be in the outer part of the join +-- as long as there is a distributed table +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) LEFT JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + id | kind | title | director +--------------------------------------------------------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | Bananas2 | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 1 | comedy | The Dinner Game2 | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 2 | horror | Psycho2 | Alfred Hitchcock + 3 | thriller | Vertigo | Alfred Hitchcock + 3 | thriller | Vertigo2 | Alfred Hitchcock + 4 | drama | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo2 | Akira Kurosawa +(10 rows) + +-- JSON_TABLE cannot be on the outer side of the join +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +LEFT JOIN LATERAL + (SELECT * + FROM my_films) AS foo on(foo.id = a); +ERROR: cannot pushdown the subquery +DETAIL: There exist a JSON_TABLE clause in the outer part of the outer join +-- JSON_TABLE cannot be on the FROM clause alone +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) as foo +WHERE b > + (SELECT count(*) + FROM my_films WHERE id = foo.a); +ERROR: correlated subqueries are not supported when the FROM clause contains JSON_TABLE +-- we can recursively plan json_tables on set operations +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT id FROM test_table ORDER BY id ASC LIMIT 1); +DEBUG: generating subplan XXX_1 for subquery SELECT id FROM JSON_TABLE('[{"a": 10, "b": 20}, {"a": 30, "b": 40}]'::jsonb, '$[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY) PLAN (json_table_path_1)) ORDER BY id LIMIT 1 +DEBUG: generating subplan XXX_2 for subquery SELECT id FROM JSON_TABLE('[{"a": 10, "b": 20}, {"a": 30, "b": 40}]'::jsonb, '$[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY) PLAN (json_table_path_1)) ORDER BY id LIMIT 1 +DEBUG: push down of limit count: 1 +DEBUG: generating subplan XXX_3 for subquery SELECT id FROM pg15_json.test_table ORDER BY id LIMIT 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT intermediate_result.id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer) UNION SELECT intermediate_result.id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(id integer) UNION SELECT intermediate_result.id FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(id bigint) + id +--------------------------------------------------------------------- + 1 +(1 row) + +-- LIMIT in subquery not supported when json_table exists +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +JOIN LATERAL + (SELECT * + FROM my_films WHERE json_table.id = a LIMIT 1) as foo ON (true); +ERROR: cannot push down this subquery +DETAIL: Limit clause is currently unsupported when a lateral subquery references a column from a JSON_TABLE +-- a little more complex query with multiple json_table +SELECT + director1 AS director, title1, kind1, title2, kind2 +FROM + my_films, + JSON_TABLE ( js, '$.favorites' AS favs COLUMNS ( + NESTED PATH '$[*]' AS films1 COLUMNS ( + kind1 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film1 COLUMNS ( + title1 text PATH '$.title', + director1 text PATH '$.director') + ), + NESTED PATH '$[*]' AS films2 COLUMNS ( + kind2 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film2 COLUMNS ( + title2 text PATH '$.title', + director2 text PATH '$.director' + ) + ) + ) + PLAN (favs INNER ((films1 INNER film1) CROSS (films2 INNER film2))) + ) AS jt + WHERE kind1 > kind2 AND director1 = director2 + ORDER BY 1,2,3,4; + director | title1 | kind1 | title2 | kind2 +--------------------------------------------------------------------- + Alfred Hitchcock | Vertigo | thriller | Psycho | horror + Alfred Hitchcock | Vertigo2 | thriller | Psycho2 | horror +(2 rows) + +RESET client_min_messages; +-- test some utility functions on the target list & where clause +select jsonb_path_exists(js, '$.favorites') from my_films; + jsonb_path_exists +--------------------------------------------------------------------- + t + t +(2 rows) + +select bool_and(JSON_EXISTS(js, '$.favorites.films.title')) from my_films; + bool_and +--------------------------------------------------------------------- + t +(1 row) + +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); + count +--------------------------------------------------------------------- + 2 +(1 row) + +SELECT count(*) FROM my_films WHERE JSON_EXISTS(js, '$.favorites.films.title'); + count +--------------------------------------------------------------------- + 2 +(1 row) + +-- check constraint with json_exists +create table user_profiles ( + id bigserial, + addresses jsonb, + anyjson jsonb, + check (json_exists( addresses, '$.main' )) +); +select create_distributed_table('user_profiles', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +insert into user_profiles (addresses) VALUES (JSON_SCALAR('1')); +ERROR: new row for relation "user_profiles_1687008" violates check constraint "user_profiles_addresses_check" +DETAIL: Failing row contains (1, "1", null). +CONTEXT: while executing command on localhost:xxxxx +insert into user_profiles (addresses) VALUES ('{"main":"value"}'); +-- we cannot insert because WITH UNIQUE KEYS +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITH UNIQUE KEYS)); +ERROR: duplicate JSON object key value +-- we can insert with +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITHOUT UNIQUE KEYS)) RETURNING *; + id | addresses | anyjson +--------------------------------------------------------------------- + 4 | {"main": "value"} | +(1 row) + +TRUNCATE user_profiles; +INSERT INTO user_profiles (anyjson) VALUES ('12'), ('"abc"'), ('[1,2,3]'), ('{"a":12}'); +select anyjson, anyjson is json array as json_array, anyjson is json object as json_object, anyjson is json scalar as json_scalar, +anyjson is json with UNIQUE keys +from user_profiles WHERE anyjson IS NOT NULL ORDER BY 1; + anyjson | json_array | json_object | json_scalar | ?column? +--------------------------------------------------------------------- + "abc" | f | f | t | t + 12 | f | f | t | t + [1, 2, 3] | t | f | f | t + {"a": 12} | f | t | f | t +(4 rows) + +-- use json_query +SELECT i, + json_query('[{"x": "aaa"},{"x": "bbb"},{"x": "ccc"}]'::JSONB, '$[$i].x' passing id AS i RETURNING text omit quotes) +FROM generate_series(0, 3) i +JOIN my_films ON(id = i) ORDER BY 1; + i | json_query +--------------------------------------------------------------------- + 1 | bbb + 2 | ccc +(2 rows) + +-- we can use JSON_TABLE in modification queries as well +-- use log level such that we can see trace changes +SET client_min_messages TO DEBUG1; +--the JSON_TABLE subquery is recursively planned +UPDATE test_table SET VALUE = 'XXX' FROM( +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; +DEBUG: generating subplan XXX_1 for subquery SELECT jt.id, jt.kind, jt.title, jt.director FROM pg15_json.my_films, LATERAL JSON_TABLE(my_films.js, '$."favorites"[*]' AS json_table_path_1 COLUMNS (id FOR ORDINALITY, kind text PATH '$."kind"', NESTED PATH '$."films"[*]' AS json_table_path_2 COLUMNS (title text PATH '$."title"', director text PATH '$."director"')) PLAN (json_table_path_1 OUTER json_table_path_2)) jt +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE pg15_json.test_table SET value = 'XXX'::text FROM (SELECT intermediate_result.id, intermediate_result.kind, intermediate_result.title, intermediate_result.director FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, kind text, title text, director text)) foo WHERE (foo.id OPERATOR(pg_catalog.=) test_table.id) +-- Subquery with JSON table can be pushed down because two distributed tables +-- in the query are joined on distribution column +UPDATE test_table SET VALUE = 'XXX' FROM ( +SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; +-- we can pushdown with CTEs as well +WITH json_cte AS +(SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.id = test_table.id; + -- we can recursively with CTEs as well +WITH json_cte AS +(SELECT my_films.id as film_id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + id FOR ORDINALITY, + title text PATH '$.title', + director text PATH '$.director'))) AS jt ORDER BY jt.id LIMIT 1) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.film_id = test_table.id; +DEBUG: generating subplan XXX_1 for CTE json_cte: SELECT my_films.id AS film_id, jt.kind, jt.id, jt.title, jt.director FROM pg15_json.my_films, LATERAL JSON_TABLE(my_films.js, '$."favorites"[*]' AS json_table_path_1 COLUMNS (kind text PATH '$."kind"', NESTED PATH '$."films"[*]' AS json_table_path_2 COLUMNS (id FOR ORDINALITY, title text PATH '$."title"', director text PATH '$."director"')) PLAN (json_table_path_1 OUTER json_table_path_2)) jt ORDER BY jt.id LIMIT 1 +DEBUG: push down of limit count: 1 +DEBUG: Plan XXX query after replacing subqueries and CTEs: UPDATE pg15_json.test_table SET value = 'XYZ'::text FROM (SELECT intermediate_result.film_id, intermediate_result.kind, intermediate_result.id, intermediate_result.title, intermediate_result.director FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(film_id bigint, kind text, id integer, title text, director text)) json_cte WHERE (json_cte.film_id OPERATOR(pg_catalog.=) test_table.id) +SET client_min_messages TO ERROR; +DROP SCHEMA pg15_json CASCADE; diff --git a/src/test/regress/expected/pg15_json_0.out b/src/test/regress/expected/pg15_json_0.out new file mode 100644 index 000000000..c04e76814 --- /dev/null +++ b/src/test/regress/expected/pg15_json_0.out @@ -0,0 +1,9 @@ +-- +-- PG15+ test +-- +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/set_operations.out b/src/test/regress/expected/set_operations.out index 63abfe578..250c8ae7c 100644 --- a/src/test/regress/expected/set_operations.out +++ b/src/test/regress/expected/set_operations.out @@ -533,7 +533,7 @@ DEBUG: generating subplan XXX_1 for subquery SELECT x, y FROM recursive_union.t DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_2 for subquery SELECT 1, 1 FROM recursive_union.test DEBUG: Creating router plan -DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) UNION SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer) +DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) UNION SELECT intermediate_result."?column?", intermediate_result."?column?_1" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT u.x, u.y, test.y FROM ((SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) u JOIN recursive_union.test USING (x)) ORDER BY u.x, u.y DEBUG: Router planner cannot handle multi-shard select queries x | y | y @@ -1013,7 +1013,7 @@ DEBUG: generating subplan XXX_1 for subquery SELECT x, y FROM recursive_union.t DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_2 for subquery SELECT 1, 1 FROM recursive_union.test DEBUG: Creating router plan -DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) UNION SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer) +DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) UNION SELECT intermediate_result."?column?", intermediate_result."?column?_1" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT x, y FROM (SELECT u.x, test.y FROM ((SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) u JOIN recursive_union.test USING (x)) ORDER BY u.x, test.y) set_view_recursive_second ORDER BY x, y DEBUG: Router planner cannot handle multi-shard select queries x | y @@ -1030,7 +1030,7 @@ DEBUG: generating subplan XXX_1 for subquery SELECT x, y FROM recursive_union.t DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_2 for subquery SELECT 1, 1 FROM recursive_union.test DEBUG: Creating router plan -DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) UNION SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer) +DEBUG: generating subplan XXX_3 for subquery SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) UNION SELECT intermediate_result."?column?", intermediate_result."?column?_1" FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer) DEBUG: Router planner cannot handle multi-shard select queries DEBUG: generating subplan XXX_4 for subquery SELECT y FROM recursive_union.test DEBUG: Router planner cannot handle multi-shard select queries diff --git a/src/test/regress/expected/shard_rebalancer.out b/src/test/regress/expected/shard_rebalancer.out index aab94dad1..4f7fad246 100644 --- a/src/test/regress/expected/shard_rebalancer.out +++ b/src/test/regress/expected/shard_rebalancer.out @@ -2474,3 +2474,52 @@ SELECT rebalance_table_shards('test_with_all_shards_excluded', excluded_shard_li (1 row) DROP TABLE test_with_all_shards_excluded; +SET citus.shard_count TO 2; +CREATE TABLE "events.Energy Added" (user_id int, time timestamp with time zone, data jsonb, PRIMARY KEY (user_id, time )) PARTITION BY RANGE ("time"); +CREATE INDEX idx_btree_hobbies ON "events.Energy Added" USING BTREE ((data->>'location')); +SELECT create_distributed_table('"events.Energy Added"', 'user_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE "Energy Added_17634" PARTITION OF "events.Energy Added" FOR VALUES FROM ('2018-04-13 00:00:00+00') TO ('2018-04-14 00:00:00+00'); +CREATE TABLE "Energy Added_17635" PARTITION OF "events.Energy Added" FOR VALUES FROM ('2018-04-14 00:00:00+00') TO ('2018-04-15 00:00:00+00'); +create table colocated_t1 (a int); +select create_distributed_table('colocated_t1','a',colocate_with=>'"events.Energy Added"'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +create table colocated_t2 (a int); +select create_distributed_table('colocated_t2','a',colocate_with=>'"events.Energy Added"'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +create table colocated_t3 (a int); +select create_distributed_table('colocated_t3','a',colocate_with=>'"events.Energy Added"'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG4; +SELECT * FROM get_rebalance_table_shards_plan('colocated_t1', rebalance_strategy := 'by_disk_size'); +DEBUG: skipping child tables for relation named: colocated_t1 +DEBUG: Size Query: SELECT (SELECT SUM(worker_partitioned_relation_total_size(relid)) FROM (VALUES ('public."events.Energy Added_433508"')) as q(relid)) + (SELECT SUM(pg_total_relation_size(relid)) FROM (VALUES ('public.colocated_t1_433514'), ('public.colocated_t2_433516'), ('public.colocated_t3_433518')) as q(relid)); +DEBUG: skipping child tables for relation named: colocated_t1 +DEBUG: Size Query: SELECT (SELECT SUM(worker_partitioned_relation_total_size(relid)) FROM (VALUES ('public."events.Energy Added_433508"')) as q(relid)) + (SELECT SUM(pg_total_relation_size(relid)) FROM (VALUES ('public.colocated_t1_433514'), ('public.colocated_t2_433516'), ('public.colocated_t3_433518')) as q(relid)); +DEBUG: skipping child tables for relation named: colocated_t1 +DEBUG: Size Query: SELECT (SELECT SUM(worker_partitioned_relation_total_size(relid)) FROM (VALUES ('public."events.Energy Added_433509"')) as q(relid)) + (SELECT SUM(pg_total_relation_size(relid)) FROM (VALUES ('public.colocated_t1_433515'), ('public.colocated_t2_433517'), ('public.colocated_t3_433519')) as q(relid)); +DEBUG: skipping child tables for relation named: colocated_t1 +DEBUG: Size Query: SELECT (SELECT SUM(worker_partitioned_relation_total_size(relid)) FROM (VALUES ('public."events.Energy Added_433509"')) as q(relid)) + (SELECT SUM(pg_total_relation_size(relid)) FROM (VALUES ('public.colocated_t1_433515'), ('public.colocated_t2_433517'), ('public.colocated_t3_433519')) as q(relid)); + table_name | shardid | shard_size | sourcename | sourceport | targetname | targetport +--------------------------------------------------------------------- +(0 rows) + +RESET client_min_messages; +DROP TABLE "events.Energy Added", colocated_t1, colocated_t2, colocated_t3; +RESET citus.shard_count; diff --git a/src/test/regress/expected/single_node.out b/src/test/regress/expected/single_node.out index 0490d1848..5a15cc1de 100644 --- a/src/test/regress/expected/single_node.out +++ b/src/test/regress/expected/single_node.out @@ -1,3 +1,17 @@ +-- +-- SINGLE_NODE +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA single_node; SET search_path TO single_node; SET citus.shard_count TO 4; @@ -1905,10 +1919,10 @@ NOTICE: executing the command locally: UPDATE single_node.another_schema_table_ -- not that we ignore INSERT .. SELECT via coordinator as it relies on -- COPY command INSERT INTO another_schema_table SELECT * FROM another_schema_table; -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE (a IS NOT NULL) -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE (a IS NOT NULL) -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE (a IS NOT NULL) -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630518 another_schema_table WHERE (a IS NOT NULL) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT another_schema_table.a, another_schema_table.b FROM single_node.another_schema_table_90630515 another_schema_table WHERE (another_schema_table.a IS NOT NULL) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT another_schema_table.a, another_schema_table.b FROM single_node.another_schema_table_90630516 another_schema_table WHERE (another_schema_table.a IS NOT NULL) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT another_schema_table.a, another_schema_table.b FROM single_node.another_schema_table_90630517 another_schema_table WHERE (another_schema_table.a IS NOT NULL) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT another_schema_table.a, another_schema_table.b FROM single_node.another_schema_table_90630518 another_schema_table WHERE (another_schema_table.a IS NOT NULL) INSERT INTO another_schema_table SELECT b::int, a::int FROM another_schema_table; NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630515_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630515_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630516_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630516_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 @@ -1931,10 +1945,10 @@ NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_r NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630516_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630516_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630517_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630517_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630518_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630518_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630515_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630516_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630515_to_2,repartitioned_results_xxxxx_from_90630517_to_2,repartitioned_results_xxxxx_from_90630518_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630518_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630515_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630516_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630515_to_2,repartitioned_results_xxxxx_from_90630517_to_2,repartitioned_results_xxxxx_from_90630518_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630518_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) SELECT * FROM another_schema_table WHERE a = 100 ORDER BY b; NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE (a OPERATOR(pg_catalog.=) 100) ORDER BY b a | b @@ -1986,10 +2000,10 @@ NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING INSERT INTO another_schema_table SELECT * FROM another_schema_table ORDER BY a LIMIT 10 ON CONFLICT(a) DO UPDATE SET b = EXCLUDED.b + 1 RETURNING *; NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true ORDER BY a LIMIT '10'::bigint NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true ORDER BY a LIMIT '10'::bigint @@ -1999,10 +2013,10 @@ NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b a | b --------------------------------------------------------------------- 1 | @@ -2029,10 +2043,10 @@ NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value) SELECT intermediate_result.key, intermediate_result.value FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'text'::citus_copy_format) intermediate_result(value single_node.new_type)) cte_1 count --------------------------------------------------------------------- @@ -2056,10 +2070,10 @@ NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z NOTICE: executing the command locally: SELECT bool_and((z IS NULL)) AS bool_and FROM (SELECT intermediate_result.z FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(z integer)) cte_1 bool_and --------------------------------------------------------------------- @@ -2078,10 +2092,10 @@ NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z NOTICE: executing the command locally: SELECT count(DISTINCT (key)::text) AS count, count(DISTINCT (z)::text) AS count FROM (SELECT intermediate_result.key, intermediate_result.z FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, z integer)) cte_1 count | count --------------------------------------------------------------------- @@ -2158,10 +2172,10 @@ NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b -NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b NOTICE: executing the command locally: SELECT count(*) AS count 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_1 count --------------------------------------------------------------------- @@ -2179,10 +2193,10 @@ NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx NOTICE: executing the copy locally for colocated file with shard xxxxx -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z -NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT intermediate_result.key, intermediate_result.value, intermediate_result.z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z NOTICE: executing the command locally: SELECT bool_and((z IS NULL)) AS bool_and FROM (SELECT intermediate_result.z FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(z integer)) cte_1 bool_and --------------------------------------------------------------------- diff --git a/src/test/regress/expected/single_node_0.out b/src/test/regress/expected/single_node_0.out new file mode 100644 index 000000000..5efb5b840 --- /dev/null +++ b/src/test/regress/expected/single_node_0.out @@ -0,0 +1,2391 @@ +-- +-- SINGLE_NODE +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + server_version_ge_15 +--------------------------------------------------------------------- + f +(1 row) + +CREATE SCHEMA single_node; +SET search_path TO single_node; +SET citus.shard_count TO 4; +SET citus.shard_replication_factor TO 1; +SET citus.next_shard_id TO 90630500; +-- Ensure tuple data in explain analyze output is the same on all PG versions +SET citus.enable_binary_protocol = TRUE; +-- do not cache any connections for now, will enable it back soon +ALTER SYSTEM SET citus.max_cached_conns_per_worker TO 0; +-- adding the coordinator as inactive is disallowed +SELECT 1 FROM master_add_inactive_node('localhost', :master_port, groupid => 0); +ERROR: coordinator node cannot be added as inactive node +-- before adding a node we are not officially a coordinator +SELECT citus_is_coordinator(); + citus_is_coordinator +--------------------------------------------------------------------- + f +(1 row) + +-- idempotently add node to allow this test to run without add_coordinator +SET client_min_messages TO WARNING; +SELECT 1 FROM citus_set_coordinator_host('localhost', :master_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- after adding a node we are officially a coordinator +SELECT citus_is_coordinator(); + citus_is_coordinator +--------------------------------------------------------------------- + t +(1 row) + +-- coordinator cannot be disabled +SELECT 1 FROM citus_disable_node('localhost', :master_port); +ERROR: cannot change "isactive" field of the coordinator node +RESET client_min_messages; +SELECT 1 FROM master_remove_node('localhost', :master_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT count(*) FROM pg_dist_node; + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- there are no workers now, but we should still be able to create Citus tables +-- force local execution when creating the index +ALTER SYSTEM SET citus.local_shared_pool_size TO -1; +-- Postmaster might not ack SIGHUP signal sent by pg_reload_conf() immediately, +-- so we need to sleep for some amount of time to do our best to ensure that +-- postmaster reflects GUC changes. +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(0.1); + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE failover_to_local (a int); +SELECT create_distributed_table('failover_to_local', 'a', shard_count=>32); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE INDEX CONCURRENTLY ON failover_to_local(a); +WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index. + Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index. +ERROR: the total number of connections on the server is more than max_connections(100) +HINT: Consider using a higher value for max_connections +-- reset global GUC changes +ALTER SYSTEM RESET citus.local_shared_pool_size; +ALTER SYSTEM RESET citus.max_cached_conns_per_worker; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SET client_min_messages TO WARNING; +DROP TABLE failover_to_local; +RESET client_min_messages; +-- so that we don't have to update rest of the test output +SET citus.next_shard_id TO 90630500; +CREATE TABLE ref(x int, y int); +SELECT create_reference_table('ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT groupid, nodename, nodeport, isactive, shouldhaveshards, hasmetadata, metadatasynced FROM pg_dist_node; + groupid | nodename | nodeport | isactive | shouldhaveshards | hasmetadata | metadatasynced +--------------------------------------------------------------------- + 0 | localhost | 57636 | t | t | t | t +(1 row) + +DROP TABLE ref; +-- remove the coordinator to try again with create_reference_table +SELECT master_remove_node(nodename, nodeport) FROM pg_dist_node WHERE groupid = 0; + master_remove_node +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE loc(x int, y int); +SELECT citus_add_local_table_to_metadata('loc'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +SELECT groupid, nodename, nodeport, isactive, shouldhaveshards, hasmetadata, metadatasynced FROM pg_dist_node; + groupid | nodename | nodeport | isactive | shouldhaveshards | hasmetadata | metadatasynced +--------------------------------------------------------------------- + 0 | localhost | 57636 | t | t | t | t +(1 row) + +DROP TABLE loc; +-- remove the coordinator to try again with create_distributed_table +SELECT master_remove_node(nodename, nodeport) FROM pg_dist_node WHERE groupid = 0; + master_remove_node +--------------------------------------------------------------------- + +(1 row) + +-- verify the coordinator gets auto added with the localhost guc +ALTER SYSTEM SET citus.local_hostname TO '127.0.0.1'; --although not a hostname, should work for connecting locally +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(.1); -- wait to make sure the config has changed before running the GUC + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE test(x int, y int); +SELECT create_distributed_table('test','x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT groupid, nodename, nodeport, isactive, shouldhaveshards, hasmetadata, metadatasynced FROM pg_dist_node; + groupid | nodename | nodeport | isactive | shouldhaveshards | hasmetadata | metadatasynced +--------------------------------------------------------------------- + 0 | 127.0.0.1 | 57636 | t | t | t | t +(1 row) + +DROP TABLE test; +-- remove the coordinator to try again +SELECT master_remove_node(nodename, nodeport) FROM pg_dist_node WHERE groupid = 0; + master_remove_node +--------------------------------------------------------------------- + +(1 row) + +ALTER SYSTEM RESET citus.local_hostname; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(.1); -- wait to make sure the config has changed before running the GUC + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE test(x int, y int); +SELECT create_distributed_table('test','x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT groupid, nodename, nodeport, isactive, shouldhaveshards, hasmetadata, metadatasynced FROM pg_dist_node; + groupid | nodename | nodeport | isactive | shouldhaveshards | hasmetadata | metadatasynced +--------------------------------------------------------------------- + 0 | localhost | 57636 | t | t | t | t +(1 row) + +BEGIN; + -- we should not enable MX for this temporary node just because + -- it'd spawn a bg worker targeting this node + -- and that changes the connection count specific tests + -- here + SET LOCAL citus.enable_metadata_sync TO OFF; + -- cannot add workers with specific IP as long as I have a placeholder coordinator record + SELECT 1 FROM master_add_node('127.0.0.1', :worker_1_port); +ERROR: cannot add a worker node when the coordinator hostname is set to localhost +DETAIL: Worker nodes need to be able to connect to the coordinator to transfer data. +HINT: Use SELECT citus_set_coordinator_host('') to configure the coordinator hostname +COMMIT; +BEGIN; + -- we should not enable MX for this temporary node just because + -- it'd spawn a bg worker targeting this node + -- and that changes the connection count specific tests + -- here + SET LOCAL citus.enable_metadata_sync TO OFF; + -- adding localhost workers is ok + SELECT 1 FROM master_add_node('localhost', :worker_1_port); +NOTICE: shards are still on the coordinator after adding the new node +HINT: Use SELECT rebalance_table_shards(); to balance shards data between workers and coordinator or SELECT citus_drain_node('localhost',57636); to permanently move shards away from the coordinator. + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +COMMIT; +-- we don't need this node anymore +SELECT 1 FROM master_remove_node('localhost', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- set the coordinator host to something different than localhost +SELECT 1 FROM citus_set_coordinator_host('127.0.0.1'); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +BEGIN; + -- we should not enable MX for this temporary node just because + -- it'd spawn a bg worker targeting this node + -- and that changes the connection count specific tests + -- here + SET LOCAL citus.enable_metadata_sync TO OFF; + -- adding workers with specific IP is ok now + SELECT 1 FROM master_add_node('127.0.0.1', :worker_1_port); +NOTICE: shards are still on the coordinator after adding the new node +HINT: Use SELECT rebalance_table_shards(); to balance shards data between workers and coordinator or SELECT citus_drain_node('127.0.0.1',57636); to permanently move shards away from the coordinator. + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +COMMIT; +-- we don't need this node anymore +SELECT 1 FROM master_remove_node('127.0.0.1', :worker_1_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- set the coordinator host back to localhost for the remainder of tests +SELECT 1 FROM citus_set_coordinator_host('localhost'); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- should have shards setting should not really matter for a single node +SELECT 1 FROM master_set_node_property('localhost', :master_port, 'shouldhaveshards', true); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +CREATE TYPE new_type AS (n int, m text); +CREATE TABLE test_2(x int, y int, z new_type); +SELECT create_distributed_table('test_2','x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE ref(a int, b int); +SELECT create_reference_table('ref'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE local(c int, d int); +CREATE TABLE public.another_schema_table(a int, b int); +SELECT create_distributed_table('public.another_schema_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE non_binary_copy_test (key int PRIMARY KEY, value new_type); +SELECT create_distributed_table('non_binary_copy_test', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO non_binary_copy_test SELECT i, (i, 'citus9.5')::new_type FROM generate_series(0,1000)i; +-- Confirm the basics work +INSERT INTO test VALUES (1, 2), (3, 4), (5, 6), (2, 7), (4, 5); +SELECT * FROM test WHERE x = 1; + x | y +--------------------------------------------------------------------- + 1 | 2 +(1 row) + +SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 5 +(1 row) + +SELECT * FROM test ORDER BY x; + x | y +--------------------------------------------------------------------- + 1 | 2 + 2 | 7 + 3 | 4 + 4 | 5 + 5 | 6 +(5 rows) + +UPDATE test SET y = y + 1 RETURNING *; + x | y +--------------------------------------------------------------------- + 1 | 3 + 2 | 8 + 3 | 5 + 4 | 6 + 5 | 7 +(5 rows) + +WITH cte_1 AS (UPDATE test SET y = y - 1 RETURNING *) SELECT * FROM cte_1 ORDER BY 1,2; + x | y +--------------------------------------------------------------------- + 1 | 2 + 2 | 7 + 3 | 4 + 4 | 5 + 5 | 6 +(5 rows) + +-- show that we can filter remote commands +-- given that citus.grep_remote_commands, we log all commands +SET citus.log_local_commands to true; +SELECT count(*) FROM public.another_schema_table WHERE a = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM public.another_schema_table_90630515 another_schema_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- grep matches all commands +SET citus.grep_remote_commands TO "%%"; +SELECT count(*) FROM public.another_schema_table WHERE a = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM public.another_schema_table_90630515 another_schema_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 0 +(1 row) + +-- only filter a specific shard for the local execution +BEGIN; + SET LOCAL citus.grep_remote_commands TO "%90630515%"; + SELECT count(*) FROM public.another_schema_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM public.another_schema_table_90630515 another_schema_table WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- match nothing + SET LOCAL citus.grep_remote_commands TO '%nothing%'; + SELECT count(*) FROM public.another_schema_table; + count +--------------------------------------------------------------------- + 0 +(1 row) + +COMMIT; +-- only filter a specific shard for the remote execution +BEGIN; + SET LOCAL citus.enable_local_execution TO FALSE; + SET LOCAL citus.grep_remote_commands TO '%90630515%'; + SET LOCAL citus.log_remote_commands TO ON; + SELECT count(*) FROM public.another_schema_table; +NOTICE: issuing SELECT count(*) AS count FROM public.another_schema_table_90630515 another_schema_table WHERE true +DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- match nothing + SET LOCAL citus.grep_remote_commands TO '%nothing%'; + SELECT count(*) FROM public.another_schema_table; + count +--------------------------------------------------------------------- + 0 +(1 row) + +COMMIT; +RESET citus.log_local_commands; +RESET citus.grep_remote_commands; +-- Test upsert with constraint +CREATE TABLE upsert_test +( + part_key int UNIQUE, + other_col int, + third_col int +); +-- distribute the table +SELECT create_distributed_table('upsert_test', 'part_key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- do a regular insert +INSERT INTO upsert_test (part_key, other_col) VALUES (1, 1), (2, 2) RETURNING *; + part_key | other_col | third_col +--------------------------------------------------------------------- + 1 | 1 | + 2 | 2 | +(2 rows) + +SET citus.log_remote_commands to true; +-- observe that there is a conflict and the following query does nothing +INSERT INTO upsert_test (part_key, other_col) VALUES (1, 1) ON CONFLICT DO NOTHING RETURNING *; +NOTICE: executing the command locally: INSERT INTO single_node.upsert_test_90630523 AS citus_table_alias (part_key, other_col) VALUES (1, 1) ON CONFLICT DO NOTHING RETURNING part_key, other_col, third_col + part_key | other_col | third_col +--------------------------------------------------------------------- +(0 rows) + +-- same as the above with different syntax +INSERT INTO upsert_test (part_key, other_col) VALUES (1, 1) ON CONFLICT (part_key) DO NOTHING RETURNING *; +NOTICE: executing the command locally: INSERT INTO single_node.upsert_test_90630523 AS citus_table_alias (part_key, other_col) VALUES (1, 1) ON CONFLICT(part_key) DO NOTHING RETURNING part_key, other_col, third_col + part_key | other_col | third_col +--------------------------------------------------------------------- +(0 rows) + +-- again the same query with another syntax +INSERT INTO upsert_test (part_key, other_col) VALUES (1, 1) ON CONFLICT ON CONSTRAINT upsert_test_part_key_key DO NOTHING RETURNING *; +NOTICE: executing the command locally: INSERT INTO single_node.upsert_test_90630523 AS citus_table_alias (part_key, other_col) VALUES (1, 1) ON CONFLICT ON CONSTRAINT upsert_test_part_key_key_90630523 DO NOTHING RETURNING part_key, other_col, third_col + part_key | other_col | third_col +--------------------------------------------------------------------- +(0 rows) + +BEGIN; +-- force local execution +SELECT count(*) FROM upsert_test WHERE part_key = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.upsert_test_90630523 upsert_test WHERE (part_key OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + +SET citus.log_remote_commands to false; +-- multi-shard pushdown query that goes through local execution +INSERT INTO upsert_test (part_key, other_col) SELECT part_key, other_col FROM upsert_test ON CONFLICT ON CONSTRAINT upsert_test_part_key_key DO NOTHING RETURNING *; + part_key | other_col | third_col +--------------------------------------------------------------------- +(0 rows) + +-- multi-shard pull-to-coordinator query that goes through local execution +INSERT INTO upsert_test (part_key, other_col) SELECT part_key, other_col FROM upsert_test LIMIT 100 ON CONFLICT ON CONSTRAINT upsert_test_part_key_key DO NOTHING RETURNING *; + part_key | other_col | third_col +--------------------------------------------------------------------- +(0 rows) + +COMMIT; +-- to test citus local tables +select undistribute_table('upsert_test'); +NOTICE: creating a new table for single_node.upsert_test +NOTICE: moving the data of single_node.upsert_test +NOTICE: dropping the old single_node.upsert_test +NOTICE: renaming the new table to single_node.upsert_test + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +-- create citus local table +select citus_add_local_table_to_metadata('upsert_test'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +-- test the constraint with local execution +INSERT INTO upsert_test (part_key, other_col) VALUES (1, 1) ON CONFLICT ON CONSTRAINT upsert_test_part_key_key DO NOTHING RETURNING *; + part_key | other_col | third_col +--------------------------------------------------------------------- +(0 rows) + +DROP TABLE upsert_test; +CREATE TABLE relation_tracking_table_1(id int, nonid int); +SELECT create_distributed_table('relation_tracking_table_1', 'id', colocate_with := 'none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO relation_tracking_table_1 select generate_series(6, 10000, 1), 0; +CREATE or REPLACE function foo() +returns setof relation_tracking_table_1 +AS $$ +BEGIN +RETURN query select * from relation_tracking_table_1 order by 1 limit 10; +end; +$$ language plpgsql; +CREATE TABLE relation_tracking_table_2 (id int, nonid int); +-- use the relation-access in this session +select foo(); + foo +--------------------------------------------------------------------- + (6,0) + (7,0) + (8,0) + (9,0) + (10,0) + (11,0) + (12,0) + (13,0) + (14,0) + (15,0) +(10 rows) + +-- we should be able to use sequential mode, as the previous multi-shard +-- relation access has been cleaned-up +BEGIN; +SET LOCAL citus.multi_shard_modify_mode TO sequential; +INSERT INTO relation_tracking_table_2 select generate_series(6, 1000, 1), 0; +SELECT create_distributed_table('relation_tracking_table_2', '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($$single_node.relation_tracking_table_2$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) FROM relation_tracking_table_2; + count +--------------------------------------------------------------------- + 995 +(1 row) + +ROLLBACK; +BEGIN; +INSERT INTO relation_tracking_table_2 select generate_series(6, 1000, 1), 0; +SELECT create_distributed_table('relation_tracking_table_2', '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($$single_node.relation_tracking_table_2$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) FROM relation_tracking_table_2; + count +--------------------------------------------------------------------- + 995 +(1 row) + +COMMIT; +SET client_min_messages TO ERROR; +DROP TABLE relation_tracking_table_2, relation_tracking_table_1 CASCADE; +RESET client_min_messages; +CREATE SCHEMA "Quoed.Schema"; +SET search_path TO "Quoed.Schema"; +CREATE TABLE "long_constraint_upsert\_test" +( + part_key int, + other_col int, + third_col int, + CONSTRAINT "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" UNIQUE (part_key) +); +NOTICE: identifier "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" will be truncated to "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted " +-- distribute the table and create shards +SELECT create_distributed_table('"long_constraint_upsert\_test"', 'part_key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO "long_constraint_upsert\_test" (part_key, other_col) VALUES (1, 1) ON CONFLICT ON CONSTRAINT "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" DO NOTHING RETURNING *; +NOTICE: identifier "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" will be truncated to "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted " + part_key | other_col | third_col +--------------------------------------------------------------------- + 1 | 1 | +(1 row) + +ALTER TABLE "long_constraint_upsert\_test" RENAME TO simple_table_name; +INSERT INTO simple_table_name (part_key, other_col) VALUES (1, 1) ON CONFLICT ON CONSTRAINT "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" DO NOTHING RETURNING *; +NOTICE: identifier "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" will be truncated to "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted " + part_key | other_col | third_col +--------------------------------------------------------------------- +(0 rows) + +-- this is currently not supported, but once we support +-- make sure that the following query also works fine +ALTER TABLE simple_table_name RENAME CONSTRAINT "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" TO simple_constraint_name; +NOTICE: identifier "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted \aconstraint" will be truncated to "looo oooo ooooo ooooooooooooooooo oooooooo oooooooo ng quoted " +ERROR: renaming constraints belonging to distributed tables is currently unsupported +--INSERT INTO simple_table_name (part_key, other_col) VALUES (1, 1) ON CONFLICT ON CONSTRAINT simple_constraint_name DO NOTHING RETURNING *; +SET search_path TO single_node; +SET client_min_messages TO ERROR; +DROP SCHEMA "Quoed.Schema" CASCADE; +RESET client_min_messages; +-- test partitioned index creation with long name +CREATE TABLE test_index_creation1 +( + tenant_id integer NOT NULL, + timeperiod timestamp without time zone NOT NULL, + field1 integer NOT NULL, + inserted_utc timestamp without time zone NOT NULL DEFAULT now(), + PRIMARY KEY(tenant_id, timeperiod) +) PARTITION BY RANGE (timeperiod); +CREATE TABLE test_index_creation1_p2020_09_26 +PARTITION OF test_index_creation1 FOR VALUES FROM ('2020-09-26 00:00:00') TO ('2020-09-27 00:00:00'); +CREATE TABLE test_index_creation1_p2020_09_27 +PARTITION OF test_index_creation1 FOR VALUES FROM ('2020-09-27 00:00:00') TO ('2020-09-28 00:00:00'); +select create_distributed_table('test_index_creation1', 'tenant_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- should be able to create indexes with INCLUDE/WHERE +CREATE INDEX ix_test_index_creation5 ON test_index_creation1 + USING btree(tenant_id, timeperiod) + INCLUDE (field1) WHERE (tenant_id = 100); +-- test if indexes are created +SELECT 1 AS created WHERE EXISTS(SELECT * FROM pg_indexes WHERE indexname LIKE '%test_index_creation%'); + created +--------------------------------------------------------------------- + 1 +(1 row) + +-- test citus size functions in transaction with modification +CREATE TABLE test_citus_size_func (a int); +SELECT create_distributed_table('test_citus_size_func', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO test_citus_size_func VALUES(1), (2); +BEGIN; + -- DDL with citus_table_size + ALTER TABLE test_citus_size_func ADD COLUMN newcol INT; + SELECT citus_table_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- DDL with citus_relation_size + ALTER TABLE test_citus_size_func ADD COLUMN newcol INT; + SELECT citus_relation_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- DDL with citus_total_relation_size + ALTER TABLE test_citus_size_func ADD COLUMN newcol INT; + SELECT citus_total_relation_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- single shard insert with citus_table_size + INSERT INTO test_citus_size_func VALUES (3); + SELECT citus_table_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- multi shard modification with citus_table_size + INSERT INTO test_citus_size_func SELECT * FROM test_citus_size_func; + SELECT citus_table_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- single shard insert with citus_relation_size + INSERT INTO test_citus_size_func VALUES (3); + SELECT citus_relation_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- multi shard modification with citus_relation_size + INSERT INTO test_citus_size_func SELECT * FROM test_citus_size_func; + SELECT citus_relation_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- single shard insert with citus_total_relation_size + INSERT INTO test_citus_size_func VALUES (3); + SELECT citus_total_relation_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +BEGIN; + -- multi shard modification with citus_total_relation_size + INSERT INTO test_citus_size_func SELECT * FROM test_citus_size_func; + SELECT citus_total_relation_size('test_citus_size_func'); +ERROR: citus size functions cannot be called in transaction blocks which contain multi-shard data modifications +ROLLBACK; +-- we should be able to limit intermediate results +BEGIN; + SET LOCAL citus.max_intermediate_result_size TO 0; + WITH cte_1 AS (SELECT * FROM test OFFSET 0) SELECT * FROM cte_1; +ERROR: the intermediate result size exceeds citus.max_intermediate_result_size (currently 0 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. +ROLLBACK; +-- the first cte (cte_1) does not exceed the limit +-- but the second (cte_2) exceeds, so we error out +BEGIN; + SET LOCAL citus.max_intermediate_result_size TO '1kB'; + INSERT INTO test SELECT i,i from generate_series(0,1000)i; + -- only pulls 1 row, should not hit the limit + WITH cte_1 AS (SELECT * FROM test LIMIT 1) SELECT count(*) FROM cte_1; + count +--------------------------------------------------------------------- + 1 +(1 row) + + -- cte_1 only pulls 1 row, but cte_2 all rows + WITH cte_1 AS (SELECT * FROM test LIMIT 1), + cte_2 AS (SELECT * FROM test OFFSET 0) + SELECT count(*) FROM cte_1, cte_2; +ERROR: the intermediate result size exceeds citus.max_intermediate_result_size (currently 1 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. +ROLLBACK; +-- single shard and multi-shard delete +-- inside a transaction block +BEGIN; + DELETE FROM test WHERE y = 5; + INSERT INTO test VALUES (4, 5); + DELETE FROM test WHERE x = 1; + INSERT INTO test VALUES (1, 2); +COMMIT; +CREATE INDEX single_node_i1 ON test(x); +CREATE INDEX single_node_i2 ON test(x,y); +REINDEX SCHEMA single_node; +REINDEX SCHEMA CONCURRENTLY single_node; +-- keep one of the indexes +-- drop w/wout tx blocks +BEGIN; + DROP INDEX single_node_i2; +ROLLBACK; +DROP INDEX single_node_i2; +-- change the schema w/wout TX block +BEGIN; + ALTER TABLE public.another_schema_table SET SCHEMA single_node; +ROLLBACK; +ALTER TABLE public.another_schema_table SET SCHEMA single_node; +BEGIN; + TRUNCATE test; + SELECT * FROM test; + x | y +--------------------------------------------------------------------- +(0 rows) + +ROLLBACK; +VACUUM test; +VACUUM test, test_2; +VACUUM ref, test; +VACUUM ANALYZE test(x); +ANALYZE ref; +ANALYZE test_2; +VACUUM local; +VACUUM local, ref, test, test_2; +VACUUM FULL test, ref; +BEGIN; + ALTER TABLE test ADD COLUMN z INT DEFAULT 66; + SELECT count(*) FROM test WHERE z = 66; + count +--------------------------------------------------------------------- + 5 +(1 row) + +ROLLBACK; +-- explain analyze should work on a single node +EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE) + SELECT * FROM test; + QUERY PLAN +--------------------------------------------------------------------- + Custom Scan (Citus Adaptive) (actual rows=5 loops=1) + Task Count: 4 + Tuple data received from nodes: 40 bytes + Tasks Shown: One of 4 + -> Task + Tuple data received from node: 16 bytes + Node: host=localhost port=xxxxx dbname=regression + -> Seq Scan on test_90630506 test (actual rows=2 loops=1) +(8 rows) + +-- common utility command +SELECT pg_size_pretty(citus_relation_size('test'::regclass)); + pg_size_pretty +--------------------------------------------------------------------- + 24 kB +(1 row) + +-- basic view queries +CREATE VIEW single_node_view AS + SELECT count(*) as cnt FROM test t1 JOIN test t2 USING (x); +SELECT * FROM single_node_view; + cnt +--------------------------------------------------------------------- + 5 +(1 row) + +SELECT * FROM single_node_view, test WHERE test.x = single_node_view.cnt; + cnt | x | y +--------------------------------------------------------------------- + 5 | 5 | 6 +(1 row) + +-- copy in/out +BEGIN; + COPY test(x) FROM PROGRAM 'seq 32'; + SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 37 +(1 row) + + COPY (SELECT count(DISTINCT x) FROM test) TO STDOUT; +32 + INSERT INTO test SELECT i,i FROM generate_series(0,100)i; +ROLLBACK; +-- master_create_empty_shard on coordinator +BEGIN; +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'); +NOTICE: Creating placements for the append partitioned tables on the coordinator is not supported, skipping coordinator ... +ERROR: could only create 0 of 1 of required shard replicas +END; +-- alter table inside a tx block +BEGIN; + ALTER TABLE test ADD COLUMN z single_node.new_type; + INSERT INTO test VALUES (99, 100, (1, 'onder')::new_type) RETURNING *; + x | y | z +--------------------------------------------------------------------- + 99 | 100 | (1,onder) +(1 row) + +ROLLBACK; +-- prepared statements with custom types +PREPARE single_node_prepare_p1(int, int, new_type) AS + INSERT INTO test_2 VALUES ($1, $2, $3); +EXECUTE single_node_prepare_p1(1, 1, (95, 'citus9.5')::new_type); +EXECUTE single_node_prepare_p1(2 ,2, (94, 'citus9.4')::new_type); +EXECUTE single_node_prepare_p1(3 ,2, (93, 'citus9.3')::new_type); +EXECUTE single_node_prepare_p1(4 ,2, (92, 'citus9.2')::new_type); +EXECUTE single_node_prepare_p1(5 ,2, (91, 'citus9.1')::new_type); +EXECUTE single_node_prepare_p1(6 ,2, (90, 'citus9.0')::new_type); +PREPARE use_local_query_cache(int) AS SELECT count(*) FROM test_2 WHERE x = $1; +EXECUTE use_local_query_cache(1); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE use_local_query_cache(1); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE use_local_query_cache(1); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE use_local_query_cache(1); + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE use_local_query_cache(1); + count +--------------------------------------------------------------------- + 1 +(1 row) + +SET client_min_messages TO DEBUG2; +-- the 6th execution will go through the planner +-- the 7th execution will skip the planner as it uses the cache +EXECUTE use_local_query_cache(1); +DEBUG: Deferred pruning for a fast-path router query +DEBUG: Creating router plan + count +--------------------------------------------------------------------- + 1 +(1 row) + +EXECUTE use_local_query_cache(1); + count +--------------------------------------------------------------------- + 1 +(1 row) + +RESET client_min_messages; +-- partitioned table should be fine, adding for completeness +CREATE TABLE collections_list ( + key bigint, + ts timestamptz DEFAULT now(), + collection_id integer, + value numeric, + PRIMARY KEY(key, collection_id) +) PARTITION BY LIST (collection_id ); +SELECT create_distributed_table('collections_list', 'key'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE collections_list_0 + PARTITION OF collections_list (key, ts, collection_id, value) + FOR VALUES IN ( 0 ); +CREATE TABLE collections_list_1 + PARTITION OF collections_list (key, ts, collection_id, value) + FOR VALUES IN ( 1 ); +INSERT INTO collections_list SELECT i, '2011-01-01', i % 2, i * i FROM generate_series(0, 100) i; +SELECT count(*) FROM collections_list WHERE key < 10 AND collection_id = 1; + count +--------------------------------------------------------------------- + 5 +(1 row) + +SELECT count(*) FROM collections_list_0 WHERE key < 10 AND collection_id = 1; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT count(*) FROM collections_list_1 WHERE key = 11; + count +--------------------------------------------------------------------- + 1 +(1 row) + +ALTER TABLE collections_list DROP COLUMN ts; +SELECT * FROM collections_list, collections_list_0 WHERE collections_list.key=collections_list_0.key ORDER BY 1 DESC,2 DESC,3 DESC,4 DESC LIMIT 1; + key | collection_id | value | key | collection_id | value +--------------------------------------------------------------------- + 100 | 0 | 10000 | 100 | 0 | 10000 +(1 row) + +-- test hash distribution using INSERT with generate_series() function +CREATE OR REPLACE FUNCTION part_hashint4_noop(value int4, seed int8) +RETURNS int8 AS $$ +SELECT value + seed; +$$ LANGUAGE SQL IMMUTABLE; +CREATE OPERATOR CLASS part_test_int4_ops +FOR TYPE int4 +USING HASH AS +operator 1 =, +function 2 part_hashint4_noop(int4, int8); +CREATE TABLE hash_parted ( + a int, + b int +) PARTITION BY HASH (a part_test_int4_ops); +CREATE TABLE hpart0 PARTITION OF hash_parted FOR VALUES WITH (modulus 4, remainder 0); +CREATE TABLE hpart1 PARTITION OF hash_parted FOR VALUES WITH (modulus 4, remainder 1); +CREATE TABLE hpart2 PARTITION OF hash_parted FOR VALUES WITH (modulus 4, remainder 2); +CREATE TABLE hpart3 PARTITION OF hash_parted FOR VALUES WITH (modulus 4, remainder 3); +-- Disable metadata sync since citus doesn't support distributing +-- operator class for now. +SET citus.enable_metadata_sync TO OFF; +SELECT create_distributed_table('hash_parted ', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO hash_parted VALUES (1, generate_series(1, 10)); +SELECT * FROM hash_parted ORDER BY 1, 2; + a | b +--------------------------------------------------------------------- + 1 | 1 + 1 | 2 + 1 | 3 + 1 | 4 + 1 | 5 + 1 | 6 + 1 | 7 + 1 | 8 + 1 | 9 + 1 | 10 +(10 rows) + +ALTER TABLE hash_parted DETACH PARTITION hpart0; +ALTER TABLE hash_parted DETACH PARTITION hpart1; +ALTER TABLE hash_parted DETACH PARTITION hpart2; +ALTER TABLE hash_parted DETACH PARTITION hpart3; +RESET citus.enable_metadata_sync; +-- test range partition without creating partitions and inserting with generate_series() +-- should error out even in plain PG since no partition of relation "parent_tab" is found for row +-- in Citus it errors out because it fails to evaluate partition key in insert +CREATE TABLE parent_tab (id int) PARTITION BY RANGE (id); +SELECT create_distributed_table('parent_tab', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO parent_tab VALUES (generate_series(0, 3)); +ERROR: failed to evaluate partition key in insert +HINT: try using constant values for partition column +-- now it should work +CREATE TABLE parent_tab_1_2 PARTITION OF parent_tab FOR VALUES FROM (1) to (2); +ALTER TABLE parent_tab ADD COLUMN b int; +INSERT INTO parent_tab VALUES (1, generate_series(0, 3)); +SELECT * FROM parent_tab ORDER BY 1, 2; + id | b +--------------------------------------------------------------------- + 1 | 0 + 1 | 1 + 1 | 2 + 1 | 3 +(4 rows) + +-- make sure that parallel accesses are good +SET citus.force_max_query_parallelization TO ON; +SELECT * FROM test_2 ORDER BY 1 DESC; + x | y | z +--------------------------------------------------------------------- + 6 | 2 | (90,citus9.0) + 5 | 2 | (91,citus9.1) + 4 | 2 | (92,citus9.2) + 3 | 2 | (93,citus9.3) + 2 | 2 | (94,citus9.4) + 1 | 1 | (95,citus9.5) +(6 rows) + +DELETE FROM test_2 WHERE y = 1000 RETURNING *; + x | y | z +--------------------------------------------------------------------- +(0 rows) + +RESET citus.force_max_query_parallelization ; +BEGIN; + INSERT INTO test_2 VALUES (7 ,2, (83, 'citus8.3')::new_type); + SAVEPOINT s1; + INSERT INTO test_2 VALUES (9 ,1, (82, 'citus8.2')::new_type); + SAVEPOINT s2; + ROLLBACK TO SAVEPOINT s1; + SELECT * FROM test_2 WHERE z = (83, 'citus8.3')::new_type OR z = (82, 'citus8.2')::new_type; + x | y | z +--------------------------------------------------------------------- + 7 | 2 | (83,citus8.3) +(1 row) + + RELEASE SAVEPOINT s1; +COMMIT; +SELECT * FROM test_2 WHERE z = (83, 'citus8.3')::new_type OR z = (82, 'citus8.2')::new_type; + x | y | z +--------------------------------------------------------------------- + 7 | 2 | (83,citus8.3) +(1 row) + +-- final query is only intermediate result +-- we want PG 11/12/13 behave consistently, the CTEs should be MATERIALIZED +WITH cte_1 AS (SELECT * FROM test_2) SELECT * FROM cte_1 ORDER BY 1,2; + x | y | z +--------------------------------------------------------------------- + 1 | 1 | (95,citus9.5) + 2 | 2 | (94,citus9.4) + 3 | 2 | (93,citus9.3) + 4 | 2 | (92,citus9.2) + 5 | 2 | (91,citus9.1) + 6 | 2 | (90,citus9.0) + 7 | 2 | (83,citus8.3) +(7 rows) + +-- final query is router query +WITH cte_1 AS (SELECT * FROM test_2) SELECT * FROM cte_1, test_2 WHERE test_2.x = cte_1.x AND test_2.x = 7 ORDER BY 1,2; + x | y | z | x | y | z +--------------------------------------------------------------------- + 7 | 2 | (83,citus8.3) | 7 | 2 | (83,citus8.3) +(1 row) + +-- final query is a distributed query +WITH cte_1 AS (SELECT * FROM test_2) SELECT * FROM cte_1, test_2 WHERE test_2.x = cte_1.x AND test_2.y != 2 ORDER BY 1,2; + x | y | z | x | y | z +--------------------------------------------------------------------- + 1 | 1 | (95,citus9.5) | 1 | 1 | (95,citus9.5) +(1 row) + +-- query pushdown should work +SELECT + * +FROM + (SELECT x, count(*) FROM test_2 GROUP BY x) as foo, + (SELECT x, count(*) FROM test_2 GROUP BY x) as bar +WHERE + foo.x = bar.x +ORDER BY 1 DESC, 2 DESC, 3 DESC, 4 DESC +LIMIT 1; + x | count | x | count +--------------------------------------------------------------------- + 7 | 1 | 7 | 1 +(1 row) + +-- make sure that foreign keys work fine +ALTER TABLE test_2 ADD CONSTRAINT first_pkey PRIMARY KEY (x); +ALTER TABLE test ADD CONSTRAINT foreign_key FOREIGN KEY (x) REFERENCES test_2(x) ON DELETE CASCADE; +-- show that delete on test_2 cascades to test +SELECT * FROM test WHERE x = 5; + x | y +--------------------------------------------------------------------- + 5 | 6 +(1 row) + +DELETE FROM test_2 WHERE x = 5; +SELECT * FROM test WHERE x = 5; + x | y +--------------------------------------------------------------------- +(0 rows) + +INSERT INTO test_2 VALUES (5 ,2, (91, 'citus9.1')::new_type); +INSERT INTO test VALUES (5, 6); +INSERT INTO ref VALUES (1, 2), (5, 6), (7, 8); +SELECT count(*) FROM ref; + count +--------------------------------------------------------------------- + 3 +(1 row) + +SELECT * FROM ref ORDER BY a; + a | b +--------------------------------------------------------------------- + 1 | 2 + 5 | 6 + 7 | 8 +(3 rows) + +SELECT * FROM test, ref WHERE x = a ORDER BY x; + x | y | a | b +--------------------------------------------------------------------- + 1 | 2 | 1 | 2 + 5 | 6 | 5 | 6 +(2 rows) + +INSERT INTO local VALUES (1, 2), (3, 4), (7, 8); +SELECT count(*) FROM local; + count +--------------------------------------------------------------------- + 3 +(1 row) + +SELECT * FROM local ORDER BY c; + c | d +--------------------------------------------------------------------- + 1 | 2 + 3 | 4 + 7 | 8 +(3 rows) + +SELECT * FROM ref, local WHERE a = c ORDER BY a; + a | b | c | d +--------------------------------------------------------------------- + 1 | 2 | 1 | 2 + 7 | 8 | 7 | 8 +(2 rows) + +-- Check repartition joins are supported +SET citus.enable_repartition_joins TO ON; +SELECT * FROM test t1, test t2 WHERE t1.x = t2.y ORDER BY t1.x; + x | y | x | y +--------------------------------------------------------------------- + 2 | 7 | 1 | 2 + 4 | 5 | 3 | 4 + 5 | 6 | 4 | 5 +(3 rows) + +SET citus.enable_single_hash_repartition_joins TO ON; +SELECT * FROM test t1, test t2 WHERE t1.x = t2.y ORDER BY t1.x; + x | y | x | y +--------------------------------------------------------------------- + 2 | 7 | 1 | 2 + 4 | 5 | 3 | 4 + 5 | 6 | 4 | 5 +(3 rows) + +SET search_path TO public; +SET citus.enable_single_hash_repartition_joins TO OFF; +SELECT * FROM single_node.test t1, single_node.test t2 WHERE t1.x = t2.y ORDER BY t1.x; + x | y | x | y +--------------------------------------------------------------------- + 2 | 7 | 1 | 2 + 4 | 5 | 3 | 4 + 5 | 6 | 4 | 5 +(3 rows) + +SET citus.enable_single_hash_repartition_joins TO ON; +SELECT * FROM single_node.test t1, single_node.test t2 WHERE t1.x = t2.y ORDER BY t1.x; + x | y | x | y +--------------------------------------------------------------------- + 2 | 7 | 1 | 2 + 4 | 5 | 3 | 4 + 5 | 6 | 4 | 5 +(3 rows) + +SET search_path TO single_node; +SET citus.task_assignment_policy TO 'round-robin'; +SET citus.enable_single_hash_repartition_joins TO ON; +SELECT * FROM test t1, test t2 WHERE t1.x = t2.y ORDER BY t1.x; + x | y | x | y +--------------------------------------------------------------------- + 2 | 7 | 1 | 2 + 4 | 5 | 3 | 4 + 5 | 6 | 4 | 5 +(3 rows) + +SET citus.task_assignment_policy TO 'greedy'; +SELECT * FROM test t1, test t2 WHERE t1.x = t2.y ORDER BY t1.x; + x | y | x | y +--------------------------------------------------------------------- + 2 | 7 | 1 | 2 + 4 | 5 | 3 | 4 + 5 | 6 | 4 | 5 +(3 rows) + +SET citus.task_assignment_policy TO 'first-replica'; +SELECT * FROM test t1, test t2 WHERE t1.x = t2.y ORDER BY t1.x; + x | y | x | y +--------------------------------------------------------------------- + 2 | 7 | 1 | 2 + 4 | 5 | 3 | 4 + 5 | 6 | 4 | 5 +(3 rows) + +RESET citus.enable_repartition_joins; +RESET citus.enable_single_hash_repartition_joins; +-- INSERT SELECT router +BEGIN; +INSERT INTO test(x, y) SELECT x, y FROM test WHERE x = 1; +SELECT count(*) from test; + count +--------------------------------------------------------------------- + 6 +(1 row) + +ROLLBACK; +-- INSERT SELECT pushdown +BEGIN; +INSERT INTO test(x, y) SELECT x, y FROM test; +SELECT count(*) from test; + count +--------------------------------------------------------------------- + 10 +(1 row) + +ROLLBACK; +-- INSERT SELECT analytical query +BEGIN; +INSERT INTO test(x, y) SELECT count(x), max(y) FROM test; +SELECT count(*) from test; + count +--------------------------------------------------------------------- + 6 +(1 row) + +ROLLBACK; +-- INSERT SELECT repartition +BEGIN; +INSERT INTO test(x, y) SELECT y, x FROM test; +SELECT count(*) from test; + count +--------------------------------------------------------------------- + 10 +(1 row) + +ROLLBACK; +-- INSERT SELECT from reference table into distributed +BEGIN; +INSERT INTO test(x, y) SELECT a, b FROM ref; +SELECT count(*) from test; + count +--------------------------------------------------------------------- + 8 +(1 row) + +ROLLBACK; +-- INSERT SELECT from local table into distributed +BEGIN; +INSERT INTO test(x, y) SELECT c, d FROM local; +SELECT count(*) from test; + count +--------------------------------------------------------------------- + 8 +(1 row) + +ROLLBACK; +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO ref(a, b) SELECT x, y FROM test; +SELECT count(*) from ref; + count +--------------------------------------------------------------------- + 8 +(1 row) + +ROLLBACK; +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO ref(a, b) SELECT c, d FROM local; +SELECT count(*) from ref; + count +--------------------------------------------------------------------- + 6 +(1 row) + +ROLLBACK; +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO local(c, d) SELECT x, y FROM test; +SELECT count(*) from local; + count +--------------------------------------------------------------------- + 8 +(1 row) + +ROLLBACK; +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO local(c, d) SELECT a, b FROM ref; +SELECT count(*) from local; + count +--------------------------------------------------------------------- + 6 +(1 row) + +ROLLBACK; +-- Confirm that dummy placements work +SELECT count(*) FROM test WHERE false; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT count(*) FROM test WHERE false GROUP BY GROUPING SETS (x,y); + count +--------------------------------------------------------------------- +(0 rows) + +-- Confirm that they work with round-robin task assignment policy +SET citus.task_assignment_policy TO 'round-robin'; +SELECT count(*) FROM test WHERE false; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT count(*) FROM test WHERE false GROUP BY GROUPING SETS (x,y); + count +--------------------------------------------------------------------- +(0 rows) + +RESET citus.task_assignment_policy; +SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 5 +(1 row) + +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO ref(a, b) SELECT x, y FROM test; +SELECT count(*) from ref; + count +--------------------------------------------------------------------- + 8 +(1 row) + +ROLLBACK; +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO ref(a, b) SELECT c, d FROM local; +SELECT count(*) from ref; + count +--------------------------------------------------------------------- + 6 +(1 row) + +ROLLBACK; +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO local(c, d) SELECT x, y FROM test; +SELECT count(*) from local; + count +--------------------------------------------------------------------- + 8 +(1 row) + +ROLLBACK; +-- INSERT SELECT from distributed table to local table +BEGIN; +INSERT INTO local(c, d) SELECT a, b FROM ref; +SELECT count(*) from local; + count +--------------------------------------------------------------------- + 6 +(1 row) + +ROLLBACK; +-- query fails on the shards should be handled +-- nicely +SELECT x/0 FROM test; +ERROR: division by zero +CONTEXT: while executing command on localhost:xxxxx +-- Add "fake" pg_dist_transaction records and run recovery +-- to show that it is recovered +-- Temporarily disable automatic 2PC recovery +ALTER SYSTEM SET citus.recover_2pc_interval TO -1; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +BEGIN; +CREATE TABLE should_commit (value int); +PREPARE TRANSACTION 'citus_0_should_commit'; +-- zero is the coordinator's group id, so we can hard code it +INSERT INTO pg_dist_transaction VALUES (0, 'citus_0_should_commit'); +SELECT recover_prepared_transactions(); + recover_prepared_transactions +--------------------------------------------------------------------- + 1 +(1 row) + +-- the table should be seen +SELECT * FROM should_commit; + value +--------------------------------------------------------------------- +(0 rows) + +-- set the original back +ALTER SYSTEM RESET citus.recover_2pc_interval; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +RESET citus.task_executor_type; +-- make sure undistribute table works fine +ALTER TABLE test DROP CONSTRAINT foreign_key; +SELECT undistribute_table('test_2'); +NOTICE: creating a new table for single_node.test_2 +NOTICE: moving the data of single_node.test_2 +NOTICE: dropping the old single_node.test_2 +NOTICE: renaming the new table to single_node.test_2 + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +SELECT * FROM pg_dist_partition WHERE logicalrelid = 'test_2'::regclass; + logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted +--------------------------------------------------------------------- +(0 rows) + +CREATE TABLE reference_table_1 (col_1 INT UNIQUE, col_2 INT UNIQUE, UNIQUE (col_2, col_1)); +SELECT create_reference_table('reference_table_1'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE distributed_table_1 (col_1 INT UNIQUE); +SELECT create_distributed_table('distributed_table_1', 'col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE citus_local_table_1 (col_1 INT UNIQUE); +SELECT citus_add_local_table_to_metadata('citus_local_table_1'); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE partitioned_table_1 (col_1 INT UNIQUE, col_2 INT) PARTITION BY RANGE (col_1); +CREATE TABLE partitioned_table_1_100_200 PARTITION OF partitioned_table_1 FOR VALUES FROM (100) TO (200); +CREATE TABLE partitioned_table_1_200_300 PARTITION OF partitioned_table_1 FOR VALUES FROM (200) TO (300); +SELECT create_distributed_table('partitioned_table_1', 'col_1'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE citus_local_table_1 ADD CONSTRAINT fkey_1 FOREIGN KEY (col_1) REFERENCES reference_table_1(col_2); +ALTER TABLE reference_table_1 ADD CONSTRAINT fkey_2 FOREIGN KEY (col_2) REFERENCES reference_table_1(col_1); +ALTER TABLE distributed_table_1 ADD CONSTRAINT fkey_3 FOREIGN KEY (col_1) REFERENCES reference_table_1(col_1); +ALTER TABLE citus_local_table_1 ADD CONSTRAINT fkey_4 FOREIGN KEY (col_1) REFERENCES reference_table_1(col_2); +ALTER TABLE partitioned_table_1 ADD CONSTRAINT fkey_5 FOREIGN KEY (col_1) REFERENCES reference_table_1(col_2); +SELECT undistribute_table('partitioned_table_1', cascade_via_foreign_keys=>true); +NOTICE: converting the partitions of single_node.partitioned_table_1 +NOTICE: creating a new table for single_node.partitioned_table_1 +NOTICE: dropping the old single_node.partitioned_table_1 +NOTICE: renaming the new table to single_node.partitioned_table_1 +NOTICE: creating a new table for single_node.reference_table_1 +NOTICE: moving the data of single_node.reference_table_1 +NOTICE: dropping the old single_node.reference_table_1 +NOTICE: renaming the new table to single_node.reference_table_1 +NOTICE: creating a new table for single_node.distributed_table_1 +NOTICE: moving the data of single_node.distributed_table_1 +NOTICE: dropping the old single_node.distributed_table_1 +NOTICE: renaming the new table to single_node.distributed_table_1 +NOTICE: creating a new table for single_node.citus_local_table_1 +NOTICE: moving the data of single_node.citus_local_table_1 +NOTICE: dropping the old single_node.citus_local_table_1 +NOTICE: renaming the new table to single_node.citus_local_table_1 +NOTICE: creating a new table for single_node.partitioned_table_1_100_200 +NOTICE: moving the data of single_node.partitioned_table_1_100_200 +NOTICE: dropping the old single_node.partitioned_table_1_100_200 +NOTICE: renaming the new table to single_node.partitioned_table_1_100_200 +NOTICE: creating a new table for single_node.partitioned_table_1_200_300 +NOTICE: moving the data of single_node.partitioned_table_1_200_300 +NOTICE: dropping the old single_node.partitioned_table_1_200_300 +NOTICE: renaming the new table to single_node.partitioned_table_1_200_300 + undistribute_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE local_table_1 (col_1 INT UNIQUE); +CREATE TABLE local_table_2 (col_1 INT UNIQUE); +CREATE TABLE local_table_3 (col_1 INT UNIQUE); +ALTER TABLE local_table_2 ADD CONSTRAINT fkey_6 FOREIGN KEY (col_1) REFERENCES local_table_1(col_1); +ALTER TABLE local_table_3 ADD CONSTRAINT fkey_7 FOREIGN KEY (col_1) REFERENCES local_table_1(col_1); +ALTER TABLE local_table_1 ADD CONSTRAINT fkey_8 FOREIGN KEY (col_1) REFERENCES local_table_1(col_1); +SELECT citus_add_local_table_to_metadata('local_table_2', cascade_via_foreign_keys=>true); + citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +CREATE PROCEDURE call_delegation(x int) LANGUAGE plpgsql AS $$ +BEGIN + INSERT INTO test (x) VALUES ($1); +END;$$; +SELECT * FROM pg_dist_node; + nodeid | groupid | nodename | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards +--------------------------------------------------------------------- + 5 | 0 | localhost | 57636 | default | t | t | primary | default | t | t +(1 row) + +SELECT create_distributed_function('call_delegation(int)', '$1', 'test'); + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +CREATE FUNCTION function_delegation(int) RETURNS void AS $$ +BEGIN +UPDATE test SET y = y + 1 WHERE x < $1; +END; +$$ LANGUAGE plpgsql; +SELECT create_distributed_function('function_delegation(int)', '$1', 'test'); + create_distributed_function +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO DEBUG1; +CALL call_delegation(1); +DEBUG: not pushing down procedure to the same node +SELECT function_delegation(1); +DEBUG: not pushing down function to the same node + function_delegation +--------------------------------------------------------------------- + +(1 row) + +SET client_min_messages TO WARNING; +DROP TABLE test CASCADE; +CREATE OR REPLACE FUNCTION pg_catalog.get_all_active_client_backend_count() + RETURNS bigint + LANGUAGE C STRICT + AS 'citus', $$get_all_active_client_backend_count$$; +-- set the cached connections to zero +-- and execute a distributed query so that +-- we end up with zero cached connections afterwards +ALTER SYSTEM SET citus.max_cached_conns_per_worker TO 0; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +-- disable deadlock detection and re-trigger 2PC recovery +-- once more when citus.max_cached_conns_per_worker is zero +-- so that we can be sure that the connections established for +-- maintanince daemon is closed properly. +-- this is to prevent random failures in the tests (otherwise, we +-- might see connections established for this operations) +ALTER SYSTEM SET citus.distributed_deadlock_detection_factor TO -1; +ALTER SYSTEM SET citus.recover_2pc_interval TO '1ms'; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(0.1); + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +-- now that last 2PC recovery is done, we're good to disable it +ALTER SYSTEM SET citus.recover_2pc_interval TO '-1'; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +-- test alter_distributed_table UDF +CREATE TABLE adt_table (a INT, b INT); +CREATE TABLE adt_col (a INT UNIQUE, b INT); +CREATE TABLE adt_ref (a INT REFERENCES adt_col(a)); +SELECT create_distributed_table('adt_table', 'a', colocate_with:='none'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('adt_col', 'a', colocate_with:='adt_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('adt_ref', 'a', colocate_with:='adt_table'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO adt_table VALUES (1, 2), (3, 4), (5, 6); +INSERT INTO adt_col VALUES (3, 4), (5, 6), (7, 8); +INSERT INTO adt_ref VALUES (3), (5); +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text LIKE 'adt%'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_col | distributed | a | 4 + adt_ref | distributed | a | 4 + adt_table | distributed | a | 4 +(3 rows) + +SELECT STRING_AGG(table_name::text, ', ' ORDER BY 1) AS "Colocation Groups" FROM public.citus_tables WHERE table_name::text LIKE 'adt%' GROUP BY colocation_id ORDER BY 1; + Colocation Groups +--------------------------------------------------------------------- + adt_col, adt_ref, adt_table +(1 row) + +SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint + WHERE (conrelid::regclass::text = 'adt_col' OR confrelid::regclass::text = 'adt_col') ORDER BY 1; + Referencing Table | Definition +--------------------------------------------------------------------- + adt_col | UNIQUE (a) + adt_ref | FOREIGN KEY (a) REFERENCES adt_col(a) +(2 rows) + +SELECT alter_distributed_table('adt_table', shard_count:=6, cascade_to_colocated:=true); + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text LIKE 'adt%'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_col | distributed | a | 6 + adt_ref | distributed | a | 6 + adt_table | distributed | a | 6 +(3 rows) + +SELECT STRING_AGG(table_name::text, ', ' ORDER BY 1) AS "Colocation Groups" FROM public.citus_tables WHERE table_name::text LIKE 'adt%' GROUP BY colocation_id ORDER BY 1; + Colocation Groups +--------------------------------------------------------------------- + adt_col, adt_ref, adt_table +(1 row) + +SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint + WHERE (conrelid::regclass::text = 'adt_col' OR confrelid::regclass::text = 'adt_col') ORDER BY 1; + Referencing Table | Definition +--------------------------------------------------------------------- + adt_col | UNIQUE (a) + adt_ref | FOREIGN KEY (a) REFERENCES adt_col(a) +(2 rows) + +SELECT alter_distributed_table('adt_table', distribution_column:='b', colocate_with:='none'); + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text LIKE 'adt%'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_col | distributed | a | 6 + adt_ref | distributed | a | 6 + adt_table | distributed | b | 6 +(3 rows) + +SELECT STRING_AGG(table_name::text, ', ' ORDER BY 1) AS "Colocation Groups" FROM public.citus_tables WHERE table_name::text LIKE 'adt%' GROUP BY colocation_id ORDER BY 1; + Colocation Groups +--------------------------------------------------------------------- + adt_col, adt_ref + adt_table +(2 rows) + +SELECT conrelid::regclass::text AS "Referencing Table", pg_get_constraintdef(oid, true) AS "Definition" FROM pg_constraint + WHERE (conrelid::regclass::text = 'adt_col' OR confrelid::regclass::text = 'adt_col') ORDER BY 1; + Referencing Table | Definition +--------------------------------------------------------------------- + adt_col | UNIQUE (a) + adt_ref | FOREIGN KEY (a) REFERENCES adt_col(a) +(2 rows) + +SELECT * FROM adt_table ORDER BY 1; + a | b +--------------------------------------------------------------------- + 1 | 2 + 3 | 4 + 5 | 6 +(3 rows) + +SELECT * FROM adt_col ORDER BY 1; + a | b +--------------------------------------------------------------------- + 3 | 4 + 5 | 6 + 7 | 8 +(3 rows) + +SELECT * FROM adt_ref ORDER BY 1; + a +--------------------------------------------------------------------- + 3 + 5 +(2 rows) + +-- make sure that COPY (e.g., INSERT .. SELECT) and +-- alter_distributed_table works in the same TX +BEGIN; +SET LOCAL citus.enable_local_execution=OFF; +INSERT INTO adt_table SELECT x, x+1 FROM generate_series(1, 1000) x; +SELECT alter_distributed_table('adt_table', distribution_column:='a'); + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ROLLBACK; +BEGIN; +INSERT INTO adt_table SELECT x, x+1 FROM generate_series(1, 1000) x; +SELECT alter_distributed_table('adt_table', distribution_column:='a'); + alter_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT COUNT(*) FROM adt_table; + count +--------------------------------------------------------------------- + 1003 +(1 row) + +END; +SELECT table_name, citus_table_type, distribution_column, shard_count FROM public.citus_tables WHERE table_name::text = 'adt_table'; + table_name | citus_table_type | distribution_column | shard_count +--------------------------------------------------------------------- + adt_table | distributed | a | 6 +(1 row) + +\c - - - :master_port +-- sometimes Postgres is a little slow to terminate the backends +-- even if PGFinish is sent. So, to prevent any flaky tests, sleep +SELECT pg_sleep(0.1); + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +-- since max_cached_conns_per_worker == 0 at this point, the +-- backend(s) that execute on the shards will be terminated +-- so show that there no internal backends +SET search_path TO single_node; +SELECT count(*) from should_commit; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT count(*) FROM pg_stat_activity WHERE application_name LIKE 'citus_internal%'; + count +--------------------------------------------------------------------- + 0 +(1 row) + +SELECT get_all_active_client_backend_count(); + get_all_active_client_backend_count +--------------------------------------------------------------------- + 1 +(1 row) + +BEGIN; + SET LOCAL citus.shard_count TO 32; + SET LOCAL citus.force_max_query_parallelization TO ON; + SET LOCAL citus.enable_local_execution TO false; + CREATE TABLE test (a int); + SET citus.shard_replication_factor TO 1; + SELECT create_distributed_table('test', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + + SELECT count(*) FROM test; + count +--------------------------------------------------------------------- + 0 +(1 row) + + -- now, we should have additional 32 connections + SELECT count(*) FROM pg_stat_activity WHERE application_name LIKE 'citus_internal%'; + count +--------------------------------------------------------------------- + 32 +(1 row) + + -- single external connection + SELECT get_all_active_client_backend_count(); + get_all_active_client_backend_count +--------------------------------------------------------------------- + 1 +(1 row) + +ROLLBACK; +\c - - - :master_port +SET search_path TO single_node; +-- simulate that even if there is no connection slots +-- to connect, Citus can switch to local execution +SET citus.force_max_query_parallelization TO false; +SET citus.log_remote_commands TO ON; +ALTER SYSTEM SET citus.local_shared_pool_size TO -1; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +SELECT pg_sleep(0.1); + pg_sleep +--------------------------------------------------------------------- + +(1 row) + +SET citus.executor_slow_start_interval TO 10; +SELECT count(*) from another_schema_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630515 another_schema_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630516 another_schema_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630517 another_schema_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630518 another_schema_table WHERE true + count +--------------------------------------------------------------------- + 0 +(1 row) + +UPDATE another_schema_table SET b = b; +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630515 another_schema_table SET b = b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630516 another_schema_table SET b = b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630517 another_schema_table SET b = b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630518 another_schema_table SET b = b +-- INSERT .. SELECT pushdown and INSERT .. SELECT via repartitioning +-- not that we ignore INSERT .. SELECT via coordinator as it relies on +-- COPY command +INSERT INTO another_schema_table SELECT * FROM another_schema_table; +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE (a IS NOT NULL) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE (a IS NOT NULL) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE (a IS NOT NULL) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM single_node.another_schema_table_90630518 another_schema_table WHERE (a IS NOT NULL) +INSERT INTO another_schema_table SELECT b::int, a::int FROM another_schema_table; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630515_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630515_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630516_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630516_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630517_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630517_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630518_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630518_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +-- multi-row INSERTs +INSERT INTO another_schema_table VALUES (1,1), (2,2), (3,3), (4,4), (5,5),(6,6),(7,7); +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) VALUES (1,1), (5,5) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) VALUES (3,3), (4,4), (7,7) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) VALUES (6,6) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) VALUES (2,2) +-- INSERT..SELECT with re-partitioning when using local execution +BEGIN; +INSERT INTO another_schema_table VALUES (1,100); +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 (a, b) VALUES (1, 100) +INSERT INTO another_schema_table VALUES (2,100); +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 (a, b) VALUES (2, 100) +INSERT INTO another_schema_table SELECT b::int, a::int FROM another_schema_table; +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630515_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630515_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630516_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630516_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630517_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630517_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: SELECT partition_index, 'repartitioned_results_xxxxx_from_90630518_to' || '_' || partition_index::text , rows_written FROM worker_partition_query_result('repartitioned_results_xxxxx_from_90630518_to','SELECT b AS a, a AS b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true',0,'hash','{-2147483648,-1073741824,0,1073741824}'::text[],'{-1073741825,-1,1073741823,2147483647}'::text[],true) WHERE rows_written > 0 +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630515_to_0}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630516_to_1}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630515_to_2,repartitioned_results_xxxxx_from_90630517_to_2,repartitioned_results_xxxxx_from_90630518_to_2}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_results('{repartitioned_results_xxxxx_from_90630518_to_3}'::text[], 'binary'::citus_copy_format) intermediate_result(a integer, b integer) +SELECT * FROM another_schema_table WHERE a = 100 ORDER BY b; +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE (a OPERATOR(pg_catalog.=) 100) ORDER BY b + a | b +--------------------------------------------------------------------- + 100 | 1 + 100 | 2 +(2 rows) + +ROLLBACK; +-- intermediate results +WITH cte_1 AS (SELECT * FROM another_schema_table LIMIT 1000) + SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT count(*) AS count 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_1 + count +--------------------------------------------------------------------- + 7 +(1 row) + +-- this is to get ready for the next tests +TRUNCATE another_schema_table; +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +-- copy can use local execution even if there is no connection available +COPY another_schema_table(a) FROM PROGRAM 'seq 32'; +NOTICE: executing the copy locally for shard xxxxx +CONTEXT: COPY another_schema_table, line 1: "1" +NOTICE: executing the copy locally for shard xxxxx +CONTEXT: COPY another_schema_table, line 2: "2" +NOTICE: executing the copy locally for shard xxxxx +CONTEXT: COPY another_schema_table, line 3: "3" +NOTICE: executing the copy locally for shard xxxxx +CONTEXT: COPY another_schema_table, line 6: "6" +-- INSERT .. SELECT with co-located intermediate results +SET citus.log_remote_commands to false; +CREATE UNIQUE INDEX another_schema_table_pk ON another_schema_table(a); +SET citus.log_local_commands to true; +INSERT INTO another_schema_table SELECT * FROM another_schema_table LIMIT 10000 ON CONFLICT(a) DO NOTHING; +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true LIMIT '10000'::bigint +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING +INSERT INTO another_schema_table SELECT * FROM another_schema_table ORDER BY a LIMIT 10 ON CONFLICT(a) DO UPDATE SET b = EXCLUDED.b + 1 RETURNING *; +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true ORDER BY a LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true ORDER BY a LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true ORDER BY a LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true ORDER BY a LIMIT '10'::bigint +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO UPDATE SET b = (excluded.b OPERATOR(pg_catalog.+) 1) RETURNING citus_table_alias.a, citus_table_alias.b + a | b +--------------------------------------------------------------------- + 1 | + 2 | + 3 | + 4 | + 5 | + 6 | + 7 | + 8 | + 9 | + 10 | +(10 rows) + +-- INSERT .. SELECT with co-located intermediate result for non-binary input +WITH cte_1 AS +(INSERT INTO non_binary_copy_test SELECT * FROM non_binary_copy_test LIMIT 10000 ON CONFLICT (key) DO UPDATE SET value = (0, 'citus0')::new_type RETURNING value) +SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: SELECT key, value FROM single_node.non_binary_copy_test_90630519 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value FROM single_node.non_binary_copy_test_90630520 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value FROM single_node.non_binary_copy_test_90630521 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value FROM single_node.non_binary_copy_test_90630522 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value) SELECT key, value FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.value +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT intermediate_result.value FROM read_intermediate_result('XXX_1'::text, 'text'::citus_copy_format) intermediate_result(value single_node.new_type)) cte_1 + count +--------------------------------------------------------------------- + 1001 +(1 row) + +-- test with NULL columns +ALTER TABLE non_binary_copy_test ADD COLUMN z INT; +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630519, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630520, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630521, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (90630522, 'single_node', 'ALTER TABLE non_binary_copy_test ADD COLUMN z INT;') +WITH cte_1 AS +(INSERT INTO non_binary_copy_test SELECT * FROM non_binary_copy_test LIMIT 10000 ON CONFLICT (key) DO UPDATE SET value = (0, 'citus0')::new_type RETURNING z) +SELECT bool_and(z is null) FROM cte_1; +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630519 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630520 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630521 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630522 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: SELECT bool_and((z IS NULL)) AS bool_and FROM (SELECT intermediate_result.z FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(z integer)) cte_1 + bool_and +--------------------------------------------------------------------- + t +(1 row) + +-- test with type coersion (int -> text) and also NULL values with coersion +WITH cte_1 AS +(INSERT INTO non_binary_copy_test SELECT * FROM non_binary_copy_test LIMIT 10000 ON CONFLICT (key) DO UPDATE SET value = (0, 'citus0')::new_type RETURNING key, z) +SELECT count(DISTINCT key::text), count(DISTINCT z::text) FROM cte_1; +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630519 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630520 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630521 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630522 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.key, citus_table_alias.z +NOTICE: executing the command locally: SELECT count(DISTINCT (key)::text) AS count, count(DISTINCT (z)::text) AS count FROM (SELECT intermediate_result.key, intermediate_result.z FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(key integer, z integer)) cte_1 + count | count +--------------------------------------------------------------------- + 1001 | 0 +(1 row) + +-- test disabling drop and truncate for known shards +SET citus.shard_replication_factor TO 1; +CREATE TABLE test_disabling_drop_and_truncate (a int); +SELECT create_distributed_table('test_disabling_drop_and_truncate', 'a'); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102040, 'single_node', 'CREATE TABLE single_node.test_disabling_drop_and_truncate (a integer) ');SELECT worker_apply_shard_ddl_command (102040, 'single_node', 'ALTER TABLE single_node.test_disabling_drop_and_truncate OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102041, 'single_node', 'CREATE TABLE single_node.test_disabling_drop_and_truncate (a integer) ');SELECT worker_apply_shard_ddl_command (102041, 'single_node', 'ALTER TABLE single_node.test_disabling_drop_and_truncate OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102042, 'single_node', 'CREATE TABLE single_node.test_disabling_drop_and_truncate (a integer) ');SELECT worker_apply_shard_ddl_command (102042, 'single_node', 'ALTER TABLE single_node.test_disabling_drop_and_truncate OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102043, 'single_node', 'CREATE TABLE single_node.test_disabling_drop_and_truncate (a integer) ');SELECT worker_apply_shard_ddl_command (102043, 'single_node', 'ALTER TABLE single_node.test_disabling_drop_and_truncate OWNER TO postgres') + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SET citus.enable_manual_changes_to_shards TO off; +-- these should error out +DROP TABLE test_disabling_drop_and_truncate_102040; +ERROR: cannot modify "test_disabling_drop_and_truncate_102040" because it is a shard of a distributed table +HINT: Use the distributed table or set citus.enable_manual_changes_to_shards to on to modify shards directly +TRUNCATE TABLE test_disabling_drop_and_truncate_102040; +ERROR: cannot modify "test_disabling_drop_and_truncate_102040" because it is a shard of a distributed table +HINT: Use the distributed table or set citus.enable_manual_changes_to_shards to on to modify shards directly +RESET citus.enable_manual_changes_to_shards ; +-- these should work as expected +TRUNCATE TABLE test_disabling_drop_and_truncate_102040; +DROP TABLE test_disabling_drop_and_truncate_102040; +DROP TABLE test_disabling_drop_and_truncate; +-- test creating distributed or reference tables from shards +CREATE TABLE test_creating_distributed_relation_table_from_shard (a int); +SELECT create_distributed_table('test_creating_distributed_relation_table_from_shard', 'a'); +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102044, 'single_node', 'CREATE TABLE single_node.test_creating_distributed_relation_table_from_shard (a integer) ');SELECT worker_apply_shard_ddl_command (102044, 'single_node', 'ALTER TABLE single_node.test_creating_distributed_relation_table_from_shard OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102045, 'single_node', 'CREATE TABLE single_node.test_creating_distributed_relation_table_from_shard (a integer) ');SELECT worker_apply_shard_ddl_command (102045, 'single_node', 'ALTER TABLE single_node.test_creating_distributed_relation_table_from_shard OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102046, 'single_node', 'CREATE TABLE single_node.test_creating_distributed_relation_table_from_shard (a integer) ');SELECT worker_apply_shard_ddl_command (102046, 'single_node', 'ALTER TABLE single_node.test_creating_distributed_relation_table_from_shard OWNER TO postgres') +NOTICE: executing the command locally: SELECT worker_apply_shard_ddl_command (102047, 'single_node', 'CREATE TABLE single_node.test_creating_distributed_relation_table_from_shard (a integer) ');SELECT worker_apply_shard_ddl_command (102047, 'single_node', 'ALTER TABLE single_node.test_creating_distributed_relation_table_from_shard OWNER TO postgres') + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- these should error because shards cannot be used to: +-- create distributed table +SELECT create_distributed_table('test_creating_distributed_relation_table_from_shard_102044', 'a'); +ERROR: relation "test_creating_distributed_relation_table_from_shard_102044" is a shard relation +-- create reference table +SELECT create_reference_table('test_creating_distributed_relation_table_from_shard_102044'); +ERROR: relation "test_creating_distributed_relation_table_from_shard_102044" is a shard relation +RESET citus.shard_replication_factor; +DROP TABLE test_creating_distributed_relation_table_from_shard; +-- lets flush the copy often to make sure everyhing is fine +SET citus.local_copy_flush_threshold TO 1; +TRUNCATE another_schema_table; +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +NOTICE: executing the command locally: TRUNCATE TABLE single_node.another_schema_table_xxxxx CASCADE +INSERT INTO another_schema_table(a) SELECT i from generate_Series(0,10000)i; +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +NOTICE: executing the copy locally for shard xxxxx +WITH cte_1 AS +(INSERT INTO another_schema_table SELECT * FROM another_schema_table ORDER BY a LIMIT 10000 ON CONFLICT(a) DO NOTHING RETURNING *) +SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true ORDER BY a LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true ORDER BY a LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true ORDER BY a LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true ORDER BY a LIMIT '10000'::bigint +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630515 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630515'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630516 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630516'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630517 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630517'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: INSERT INTO single_node.another_schema_table_90630518 AS citus_table_alias (a, b) SELECT a, b FROM read_intermediate_result('insert_select_XXX_90630518'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer) ON CONFLICT(a) DO NOTHING RETURNING citus_table_alias.a, citus_table_alias.b +NOTICE: executing the command locally: SELECT count(*) AS count 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_1 + count +--------------------------------------------------------------------- + 0 +(1 row) + +WITH cte_1 AS +(INSERT INTO non_binary_copy_test SELECT * FROM non_binary_copy_test LIMIT 10000 ON CONFLICT (key) DO UPDATE SET value = (0, 'citus0')::new_type RETURNING z) +SELECT bool_and(z is null) FROM cte_1; +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630519 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630520 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630521 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the command locally: SELECT key, value, z FROM single_node.non_binary_copy_test_90630522 non_binary_copy_test WHERE true LIMIT '10000'::bigint +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the copy locally for colocated file with shard xxxxx +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630519 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630519'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630520 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630520'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630521 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630521'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: INSERT INTO single_node.non_binary_copy_test_90630522 AS citus_table_alias (key, value, z) SELECT key, value, z FROM read_intermediate_result('insert_select_XXX_90630522'::text, 'text'::citus_copy_format) intermediate_result(key integer, value single_node.new_type, z integer) ON CONFLICT(key) DO UPDATE SET value = ROW(0, 'citus0'::text)::single_node.new_type RETURNING citus_table_alias.z +NOTICE: executing the command locally: SELECT bool_and((z IS NULL)) AS bool_and FROM (SELECT intermediate_result.z FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(z integer)) cte_1 + bool_and +--------------------------------------------------------------------- + t +(1 row) + +RESET citus.local_copy_flush_threshold; +RESET citus.local_copy_flush_threshold; +CREATE OR REPLACE FUNCTION coordinated_transaction_should_use_2PC() +RETURNS BOOL LANGUAGE C STRICT VOLATILE AS 'citus', +$$coordinated_transaction_should_use_2PC$$; +-- a multi-shard/single-shard select that is failed over to local +-- execution doesn't start a 2PC +BEGIN; + SELECT count(*) FROM another_schema_table; +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630515 another_schema_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630516 another_schema_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630517 another_schema_table WHERE true +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630518 another_schema_table WHERE true + count +--------------------------------------------------------------------- + 10001 +(1 row) + + SELECT count(*) FROM another_schema_table WHERE a = 1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM single_node.another_schema_table_90630515 another_schema_table WHERE (a OPERATOR(pg_catalog.=) 1) + count +--------------------------------------------------------------------- + 1 +(1 row) + + WITH cte_1 as (SELECT * FROM another_schema_table LIMIT 10) + SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630515 another_schema_table WHERE true LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630516 another_schema_table WHERE true LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630517 another_schema_table WHERE true LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT a, b FROM single_node.another_schema_table_90630518 another_schema_table WHERE true LIMIT '10'::bigint +NOTICE: executing the command locally: SELECT count(*) AS count 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_1 + count +--------------------------------------------------------------------- + 10 +(1 row) + + WITH cte_1 as (SELECT * FROM another_schema_table WHERE a = 1 LIMIT 10) + SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: SELECT count(*) AS count FROM (SELECT another_schema_table.a, another_schema_table.b FROM single_node.another_schema_table_90630515 another_schema_table WHERE (another_schema_table.a OPERATOR(pg_catalog.=) 1) LIMIT 10) cte_1 + count +--------------------------------------------------------------------- + 1 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + f +(1 row) + +ROLLBACK; +-- same without a transaction block +WITH cte_1 AS (SELECT count(*) as cnt FROM another_schema_table LIMIT 1000), + cte_2 AS (SELECT coordinated_transaction_should_use_2PC() as enabled_2pc) +SELECT cnt, enabled_2pc FROM cte_1, cte_2; +NOTICE: executing the command locally: SELECT count(*) AS cnt FROM single_node.another_schema_table_90630515 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT count(*) AS cnt FROM single_node.another_schema_table_90630516 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT count(*) AS cnt FROM single_node.another_schema_table_90630517 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT count(*) AS cnt FROM single_node.another_schema_table_90630518 another_schema_table WHERE true LIMIT '1000'::bigint +NOTICE: executing the command locally: SELECT cte_1.cnt, cte_2.enabled_2pc FROM (SELECT intermediate_result.cnt FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(cnt bigint)) cte_1, (SELECT intermediate_result.enabled_2pc FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(enabled_2pc boolean)) cte_2 + cnt | enabled_2pc +--------------------------------------------------------------------- + 10001 | f +(1 row) + +-- a multi-shard modification that is failed over to local +-- execution starts a 2PC +BEGIN; + UPDATE another_schema_table SET b = b + 1; +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630515 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630516 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630517 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630518 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +ROLLBACK; +-- a multi-shard modification that is failed over to local +-- execution starts a 2PC +BEGIN; + WITH cte_1 AS (UPDATE another_schema_table SET b = b + 1 RETURNING *) + SELECT count(*) FROM cte_1; +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630515 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630516 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630517 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630518 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: SELECT count(*) AS count 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_1 + count +--------------------------------------------------------------------- + 10001 +(1 row) + + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +ROLLBACK; +-- same without transaction block +WITH cte_1 AS (UPDATE another_schema_table SET b = b + 1 RETURNING *) +SELECT coordinated_transaction_should_use_2PC(); +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630515 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630516 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630517 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630518 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) RETURNING a, b +NOTICE: executing the command locally: SELECT single_node.coordinated_transaction_should_use_2pc() AS coordinated_transaction_should_use_2pc + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +-- a single-shard modification that is failed over to local +-- starts 2PC execution +BEGIN; + UPDATE another_schema_table SET b = b + 1 WHERE a = 1; +NOTICE: executing the command locally: UPDATE single_node.another_schema_table_90630515 another_schema_table SET b = (b OPERATOR(pg_catalog.+) 1) WHERE (a OPERATOR(pg_catalog.=) 1) + SELECT coordinated_transaction_should_use_2PC(); + coordinated_transaction_should_use_2pc +--------------------------------------------------------------------- + t +(1 row) + +ROLLBACK; +-- if the local execution is disabled, we cannot failover to +-- local execution and the queries would fail +SET citus.enable_local_execution TO false; +SELECT count(*) from another_schema_table; +ERROR: the total number of connections on the server is more than max_connections(100) +HINT: This command supports local execution. Consider enabling local execution using SET citus.enable_local_execution TO true; +UPDATE another_schema_table SET b = b; +ERROR: the total number of connections on the server is more than max_connections(100) +HINT: This command supports local execution. Consider enabling local execution using SET citus.enable_local_execution TO true; +INSERT INTO another_schema_table SELECT * FROM another_schema_table; +ERROR: the total number of connections on the server is more than max_connections(100) +HINT: This command supports local execution. Consider enabling local execution using SET citus.enable_local_execution TO true; +INSERT INTO another_schema_table SELECT b::int, a::int FROM another_schema_table; +ERROR: the total number of connections on the server is more than max_connections(100) +HINT: This command supports local execution. Consider enabling local execution using SET citus.enable_local_execution TO true; +WITH cte_1 AS (SELECT * FROM another_schema_table LIMIT 1000) + SELECT count(*) FROM cte_1; +ERROR: the total number of connections on the server is more than max_connections(100) +HINT: This command supports local execution. Consider enabling local execution using SET citus.enable_local_execution TO true; +INSERT INTO another_schema_table VALUES (1,1), (2,2), (3,3), (4,4), (5,5),(6,6),(7,7); +ERROR: the total number of connections on the server is more than max_connections(100) +HINT: This command supports local execution. Consider enabling local execution using SET citus.enable_local_execution TO true; +-- copy fails if local execution is disabled and there is no connection slot +COPY another_schema_table(a) FROM PROGRAM 'seq 32'; +ERROR: could not find an available connection +HINT: Set citus.max_shared_pool_size TO -1 to let COPY command finish +CONTEXT: COPY another_schema_table, line 1: "1" +-- set the values to originals back +ALTER SYSTEM RESET citus.max_cached_conns_per_worker; +ALTER SYSTEM RESET citus.distributed_deadlock_detection_factor; +ALTER SYSTEM RESET citus.recover_2pc_interval; +ALTER SYSTEM RESET citus.distributed_deadlock_detection_factor; +ALTER SYSTEM RESET citus.local_shared_pool_size; +SELECT pg_reload_conf(); + pg_reload_conf +--------------------------------------------------------------------- + t +(1 row) + +-- suppress notices +SET client_min_messages TO error; +-- cannot remove coordinator since a reference table exists on coordinator and no other worker nodes are added +SELECT 1 FROM master_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 single_node.ref +HINT: To proceed, either drop the tables or use undistribute_table() function to convert them to local tables +-- Cleanup +DROP SCHEMA single_node CASCADE; +-- Remove the coordinator again +SELECT 1 FROM master_remove_node('localhost', :master_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- restart nodeid sequence so that multi_cluster_management still has the same +-- nodeids +ALTER SEQUENCE pg_dist_node_nodeid_seq RESTART 1; diff --git a/src/test/regress/expected/split_shard_release_dsm.out b/src/test/regress/expected/split_shard_release_dsm.out new file mode 100644 index 000000000..95cc210bb --- /dev/null +++ b/src/test/regress/expected/split_shard_release_dsm.out @@ -0,0 +1,45 @@ +-- Test Secneario +-- 1) Setup shared memory segment by calling worker_split_shard_replication_setup. +-- 2) Redo step 1 as the earlier memory. Redoing will trigger warning as earlier memory isn't released. +-- 3) Execute worker_split_shard_release_dsm to release the dynamic shared memory segment +-- 4) Redo step 1 and expect no warning as the earlier memory is cleanedup. +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 +\c - - - :worker_1_port +SET search_path TO split_shard_replication_setup_schema; +SET client_min_messages TO ERROR; +SELECT count(*) FROM pg_catalog.worker_split_shard_replication_setup(ARRAY[ + ROW(1, 'id', 2, '-2147483648', '-1', :worker_1_node)::pg_catalog.split_shard_info, + ROW(1, 'id', 3, '0', '2147483647', :worker_1_node)::pg_catalog.split_shard_info + ]); + count +--------------------------------------------------------------------- + 1 +(1 row) + +SET client_min_messages TO WARNING; +SELECT count(*) FROM pg_catalog.worker_split_shard_replication_setup(ARRAY[ + ROW(1, 'id', 2, '-2147483648', '-1', :worker_1_node)::pg_catalog.split_shard_info, + ROW(1, 'id', 3, '0', '2147483647', :worker_1_node)::pg_catalog.split_shard_info + ]); +WARNING: Previous split shard worflow was not successfully and could not complete the cleanup phase. Continuing with the current split shard workflow. + count +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT pg_catalog.worker_split_shard_release_dsm(); + worker_split_shard_release_dsm +--------------------------------------------------------------------- + +(1 row) + +SELECT count(*) FROM pg_catalog.worker_split_shard_replication_setup(ARRAY[ + ROW(1, 'id', 2, '-2147483648', '-1', :worker_1_node)::pg_catalog.split_shard_info, + ROW(1, 'id', 3, '0', '2147483647', :worker_1_node)::pg_catalog.split_shard_info + ]); + count +--------------------------------------------------------------------- + 1 +(1 row) + diff --git a/src/test/regress/expected/start_stop_metadata_sync.out b/src/test/regress/expected/start_stop_metadata_sync.out index 0126f6fed..daa23aecd 100644 --- a/src/test/regress/expected/start_stop_metadata_sync.out +++ b/src/test/regress/expected/start_stop_metadata_sync.out @@ -1,6 +1,7 @@ CREATE SCHEMA start_stop_metadata_sync; SET search_path TO "start_stop_metadata_sync"; SET citus.next_shard_id TO 980000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 980000; SET client_min_messages TO WARNING; SET citus.shard_count TO 4; SET citus.shard_replication_factor TO 1; @@ -157,12 +158,12 @@ SELECT * FROM test_matview; SELECT * FROM pg_dist_partition WHERE logicalrelid::text LIKE 'events%' ORDER BY logicalrelid::text; logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted --------------------------------------------------------------------- - events | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 1390012 | s | f - events_2021_feb | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 1390012 | s | f - events_2021_jan | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 1390012 | s | f - events_replicated | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 1390013 | c | f - events_replicated_2021_feb | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 1390013 | c | f - events_replicated_2021_jan | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 1390013 | c | f + events | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 980000 | s | f + events_2021_feb | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 980000 | s | f + events_2021_jan | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 980000 | s | f + events_replicated | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 980001 | c | f + events_replicated_2021_feb | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 980001 | c | f + events_replicated_2021_jan | h | {VAR :varno 1 :varattno 1 :vartype 1184 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location -1} | 980001 | c | f (6 rows) SELECT count(*) > 0 FROM pg_dist_node; diff --git a/src/test/regress/expected/stat_statements.out b/src/test/regress/expected/stat_statements.out index 1bfeba544..537bb4e9b 100644 --- a/src/test/regress/expected/stat_statements.out +++ b/src/test/regress/expected/stat_statements.out @@ -3,9 +3,9 @@ -- -- tests citus_stat_statements functionality SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int > 13 AS server_version_above_thirteen +SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 \gset -\if :server_version_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'on'; \endif -- check if pg_stat_statements is available @@ -72,7 +72,7 @@ select query, calls from citus_stat_statements(); insert into test values($1) | 1 (1 row) -\if :server_version_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'off'; \else set citus.stat_statements_track = 'none'; @@ -88,7 +88,7 @@ select query, calls from citus_stat_statements(); insert into test values($1) | 1 (1 row) -\if :server_version_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'on'; \else RESET citus.stat_statements_track; @@ -646,6 +646,6 @@ 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_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'off'; \endif diff --git a/src/test/regress/expected/subquery_complex_target_list.out b/src/test/regress/expected/subquery_complex_target_list.out index 934f89224..2ad0bfc70 100644 --- a/src/test/regress/expected/subquery_complex_target_list.out +++ b/src/test/regress/expected/subquery_complex_target_list.out @@ -120,7 +120,7 @@ DEBUG: push down of limit count: 3 DEBUG: generating subplan XXX_2 for subquery SELECT (min(value_3) OPERATOR(pg_catalog.*) (2)::double precision), (max(value_3) OPERATOR(pg_catalog./) (2)::double precision), sum(value_3) AS sum, count(value_3) AS count, avg(value_3) AS avg FROM public.users_table ORDER BY (min(value_3) OPERATOR(pg_catalog.*) (2)::double precision) DESC LIMIT 3 DEBUG: push down of limit count: 3 DEBUG: generating subplan XXX_3 for subquery SELECT min("time") AS min, max("time") AS max, count("time") AS count, count(*) FILTER (WHERE (user_id OPERATOR(pg_catalog.=) 3)) AS cnt_with_filter, count(*) FILTER (WHERE ((user_id)::text OPERATOR(pg_catalog.~~) '%3%'::text)) AS cnt_with_filter_2 FROM public.users_table ORDER BY (min("time")) DESC LIMIT 3 -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo."?column?", foo."?column?_1" AS "?column?", foo.sum, foo.count, foo.avg, bar."?column?", bar."?column?_1" AS "?column?", bar.sum, bar.count, bar.avg, baz.min, baz.max, baz.count, baz.cnt_with_filter, baz.cnt_with_filter_2 FROM (SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?", intermediate_result.sum, intermediate_result.count, intermediate_result.avg FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer, sum bigint, count double precision, avg bigint)) foo("?column?", "?column?_1", sum, count, avg), (SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?", intermediate_result.sum, intermediate_result.count, intermediate_result.avg FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" double precision, "?column?_1" double precision, sum double precision, count bigint, avg double precision)) bar("?column?", "?column?_1", sum, count, avg), (SELECT intermediate_result.min, intermediate_result.max, intermediate_result.count, intermediate_result.cnt_with_filter, intermediate_result.cnt_with_filter_2 FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(min timestamp without time zone, max timestamp without time zone, count bigint, cnt_with_filter bigint, cnt_with_filter_2 bigint)) baz ORDER BY foo."?column?" DESC +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo."?column?", foo."?column?_1", foo.sum, foo.count, foo.avg, bar."?column?", bar."?column?_1", bar.sum, bar.count, bar.avg, baz.min, baz.max, baz.count, baz.cnt_with_filter, baz.cnt_with_filter_2 FROM (SELECT intermediate_result."?column?", intermediate_result."?column?_1", intermediate_result.sum, intermediate_result.count, intermediate_result.avg FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer, sum bigint, count double precision, avg bigint)) foo("?column?", "?column?_1", sum, count, avg), (SELECT intermediate_result."?column?", intermediate_result."?column?_1", intermediate_result.sum, intermediate_result.count, intermediate_result.avg FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result("?column?" double precision, "?column?_1" double precision, sum double precision, count bigint, avg double precision)) bar("?column?", "?column?_1", sum, count, avg), (SELECT intermediate_result.min, intermediate_result.max, intermediate_result.count, intermediate_result.cnt_with_filter, intermediate_result.cnt_with_filter_2 FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(min timestamp without time zone, max timestamp without time zone, count bigint, cnt_with_filter bigint, cnt_with_filter_2 bigint)) baz ORDER BY foo."?column?" DESC ?column? | ?column? | sum | count | avg | ?column? | ?column? | sum | count | avg | min | max | count | cnt_with_filter | cnt_with_filter_2 --------------------------------------------------------------------- 2 | 3 | 376 | 101 | 4 | 0 | 2.5 | 273 | 101 | 2.7029702970297 | Wed Nov 22 18:19:49.944985 2017 | Thu Nov 23 17:30:34.635085 2017 | 101 | 17 | 17 diff --git a/src/test/regress/expected/subquery_in_where.out b/src/test/regress/expected/subquery_in_where.out index c5ffc8d93..eb56acd87 100644 --- a/src/test/regress/expected/subquery_in_where.out +++ b/src/test/regress/expected/subquery_in_where.out @@ -476,7 +476,7 @@ DEBUG: generating subplan XXX_2 for subquery SELECT user_id AS user_id_2 FROM p DEBUG: generating subplan XXX_3 for subquery SELECT value_2 FROM public.events_table DEBUG: generating subplan XXX_4 for subquery SELECT t1.user_id, t2.user_id_2 FROM ((SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) t1 JOIN (SELECT intermediate_result.user_id_2 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id_2 integer)) t2 ON ((t1.user_id OPERATOR(pg_catalog.=) t2.user_id_2))) WHERE (t1.user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.value_2 FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer))) DEBUG: generating subplan XXX_5 for subquery SELECT 1, 2 FROM public.events_table WHERE (value_2 OPERATOR(pg_catalog.=) user_id) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum(user_id) AS sum FROM (SELECT intermediate_result.user_id, intermediate_result.user_id_2 FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, user_id_2 integer)) t3 WHERE (EXISTS (SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?" FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer))) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum(user_id) AS sum FROM (SELECT intermediate_result.user_id, intermediate_result.user_id_2 FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, user_id_2 integer)) t3 WHERE (EXISTS (SELECT intermediate_result."?column?", intermediate_result."?column?_1" FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer))) sum --------------------------------------------------------------------- 67 @@ -530,7 +530,7 @@ DEBUG: generating subplan XXX_2 for subquery SELECT user_id AS user_id_2 FROM p DEBUG: generating subplan XXX_3 for subquery SELECT value_2 FROM public.events_table DEBUG: generating subplan XXX_4 for subquery SELECT t1.user_id, t2.user_id_2 FROM ((SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) t1 JOIN (SELECT intermediate_result.user_id_2 FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id_2 integer)) t2 ON ((t1.user_id OPERATOR(pg_catalog.=) t2.user_id_2))) WHERE (t1.user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.value_2 FROM read_intermediate_result('XXX_3'::text, 'binary'::citus_copy_format) intermediate_result(value_2 integer))) DEBUG: generating subplan XXX_5 for subquery SELECT 1, 2 FROM public.events_table WHERE (value_2 OPERATOR(pg_catalog.=) (user_id OPERATOR(pg_catalog.+) 6)) -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum(user_id) AS sum FROM (SELECT intermediate_result.user_id, intermediate_result.user_id_2 FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, user_id_2 integer)) t3 WHERE (NOT (EXISTS (SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?" FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer)))) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum(user_id) AS sum FROM (SELECT intermediate_result.user_id, intermediate_result.user_id_2 FROM read_intermediate_result('XXX_4'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, user_id_2 integer)) t3 WHERE (NOT (EXISTS (SELECT intermediate_result."?column?", intermediate_result."?column?_1" FROM read_intermediate_result('XXX_5'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer)))) sum --------------------------------------------------------------------- 67 @@ -558,7 +558,7 @@ WHERE row(user_id, value_1) = DEBUG: generating subplan XXX_1 for subquery SELECT (min(user_id) OPERATOR(pg_catalog.+) 1), (min(user_id) OPERATOR(pg_catalog.+) 1) FROM public.events_table DEBUG: push down of limit count: 10 DEBUG: generating subplan XXX_2 for subquery SELECT user_id, value_1 FROM public.users_table ORDER BY user_id, value_1 LIMIT 10 -DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, value_1 FROM (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)) t3 WHERE ((user_id, value_1) OPERATOR(pg_catalog.=) (SELECT intermediate_result."?column?", intermediate_result."?column?_1" AS "?column?" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer))) +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, value_1 FROM (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)) t3 WHERE ((user_id, value_1) OPERATOR(pg_catalog.=) (SELECT intermediate_result."?column?", intermediate_result."?column?_1" FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result("?column?" integer, "?column?_1" integer))) user_id | value_1 --------------------------------------------------------------------- (0 rows) diff --git a/src/test/regress/expected/subquery_view.out b/src/test/regress/expected/subquery_view.out index 535e356d5..32354e329 100644 --- a/src/test/regress/expected/subquery_view.out +++ b/src/test/regress/expected/subquery_view.out @@ -578,11 +578,13 @@ SELECT create_reference_table('reference_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 reference_table JOIN cte USING (text_col); - QUERY PLAN +$Q$); + coordinator_plan_with_subplans --------------------------------------------------------------------- Custom Scan (Citus Adaptive) -> Distributed Subplan XXX_1 @@ -590,38 +592,17 @@ EXPLAIN (COSTS OFF) WITH cte AS ( -> Distributed Subplan XXX_2 -> Custom Scan (Citus Adaptive) Task Count: 1 - Tasks Shown: All - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Hash Left Join - Hash Cond: (intermediate_result.usesysid = u.oid) - -> Hash Left Join - Hash Cond: (intermediate_result.datid = d.oid) - -> Function Scan on read_intermediate_result intermediate_result - -> Hash - -> Seq Scan on pg_database d - -> Hash - -> Seq Scan on pg_authid u Task Count: 1 - Tasks Shown: All - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Merge Join - Merge Cond: (intermediate_result.application_name = reference_table.text_col) - -> Sort - Sort Key: intermediate_result.application_name - -> Function Scan on read_intermediate_result intermediate_result - -> Sort - Sort Key: reference_table.text_col - -> Seq Scan on reference_table_1512000 reference_table -(30 rows) +(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; +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 reference_table JOIN cte USING (text_col); - QUERY PLAN +$Q$); + coordinator_plan_with_subplans --------------------------------------------------------------------- Custom Scan (Citus Adaptive) -> Distributed Subplan XXX_1 @@ -629,18 +610,7 @@ EXPLAIN (COSTS OFF) WITH cte AS ( -> Function Scan on pg_stat_get_activity s -> Function Scan on pg_lock_status l Task Count: 1 - Tasks Shown: All - -> Task - Node: host=localhost port=xxxxx dbname=regression - -> Merge Join - Merge Cond: (intermediate_result.text_col = reference_table.text_col) - -> Sort - Sort Key: intermediate_result.text_col - -> Function Scan on read_intermediate_result intermediate_result - -> Sort - Sort Key: reference_table.text_col - -> Seq Scan on reference_table_1512000 reference_table -(17 rows) +(6 rows) DROP SCHEMA subquery_view CASCADE; NOTICE: drop cascades to 19 other objects diff --git a/src/test/regress/expected/upgrade_list_citus_objects.out b/src/test/regress/expected/upgrade_list_citus_objects.out index 78d540731..5b8b0eac1 100644 --- a/src/test/regress/expected/upgrade_list_citus_objects.out +++ b/src/test/regress/expected/upgrade_list_citus_objects.out @@ -65,6 +65,7 @@ ORDER BY 1; 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_delete_colocation_metadata(integer) + function citus_internal_delete_partition_metadata(regclass) function citus_internal_delete_shard_metadata(bigint) function citus_internal_global_blocked_processes() function citus_internal_local_blocked_processes() @@ -122,6 +123,7 @@ ORDER BY 1; function coord_combine_agg_sfunc(internal,oid,cstring,anyelement) function create_distributed_function(regprocedure,text,text,boolean) function create_distributed_table(regclass,text,citus.distribution_type,text,integer) + function create_distributed_table_concurrently(regclass,text,citus.distribution_type,text,integer) function create_intermediate_result(text,text) function create_reference_table(regclass) function create_time_partitions(regclass,interval,timestamp with time zone,timestamp with time zone) @@ -228,7 +230,8 @@ ORDER BY 1; function worker_partitioned_table_size(regclass) function worker_record_sequence_dependency(regclass,regclass,name) function worker_save_query_explain_analyze(text,jsonb) - function worker_split_copy(bigint,split_copy_info[]) + function worker_split_copy(bigint,text,split_copy_info[]) + function worker_split_shard_release_dsm() function worker_split_shard_replication_setup(split_shard_info[]) schema citus schema citus_internal diff --git a/src/test/regress/expected/window_functions.out b/src/test/regress/expected/window_functions.out index 6657c3670..2e88f5b51 100644 --- a/src/test/regress/expected/window_functions.out +++ b/src/test/regress/expected/window_functions.out @@ -1,6 +1,11 @@ +-- +-- 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 @@ -1382,20 +1387,15 @@ LIMIT 5; -> Task Node: host=localhost port=xxxxx dbname=regression -> Limit - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id -> WindowAgg - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, (('1'::numeric / ('1'::numeric + avg(users_table.value_1)))) - Presorted Key: users_table.user_id - -> GroupAggregate + -> HashAggregate Group Key: users_table.user_id, users_table.value_2 - -> Incremental Sort - Sort Key: users_table.user_id, users_table.value_2 - Presorted Key: users_table.user_id - -> Index Scan using is_index1_1400256 on users_table_1400256 users_table -(22 rows) + -> Seq Scan on users_table_1400256 users_table +(17 rows) EXPLAIN (COSTS FALSE) SELECT @@ -1418,20 +1418,15 @@ LIMIT 5; -> Task Node: host=localhost port=xxxxx dbname=regression -> Limit - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id -> WindowAgg - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, (('1'::numeric / ('1'::numeric + avg(users_table.value_1)))) - Presorted Key: users_table.user_id - -> GroupAggregate + -> HashAggregate Group Key: users_table.user_id, users_table.value_2 - -> Incremental Sort - Sort Key: users_table.user_id, users_table.value_2 - Presorted Key: users_table.user_id - -> Index Scan using is_index1_1400256 on users_table_1400256 users_table -(22 rows) + -> Seq Scan on users_table_1400256 users_table +(17 rows) EXPLAIN (COSTS FALSE) SELECT @@ -1443,7 +1438,7 @@ FROM GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC LIMIT 5; - QUERY PLAN + QUERY PLAN --------------------------------------------------------------------- Limit -> Sort @@ -1454,20 +1449,15 @@ LIMIT 5; -> Task Node: host=localhost port=xxxxx dbname=regression -> Limit - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id -> WindowAgg - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, ((1 / (1 + sum(users_table.value_2)))) - Presorted Key: users_table.user_id - -> GroupAggregate + -> HashAggregate Group Key: users_table.user_id, users_table.value_2 - -> Incremental Sort - Sort Key: users_table.user_id, users_table.value_2 - Presorted Key: users_table.user_id - -> Index Scan using is_index1_1400256 on users_table_1400256 users_table -(22 rows) + -> Seq Scan on users_table_1400256 users_table +(17 rows) EXPLAIN (COSTS FALSE) SELECT @@ -1479,7 +1469,7 @@ FROM GROUP BY user_id, value_2 ORDER BY user_id, avg(value_1) DESC LIMIT 5; - QUERY PLAN + QUERY PLAN --------------------------------------------------------------------- Limit -> Sort @@ -1490,26 +1480,23 @@ LIMIT 5; -> Task Node: host=localhost port=xxxxx dbname=regression -> Limit - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - Presorted Key: users_table.user_id -> WindowAgg - -> Incremental Sort + -> Sort Sort Key: users_table.user_id, (sum(users_table.value_2)) - Presorted Key: users_table.user_id - -> GroupAggregate + -> HashAggregate Group Key: users_table.user_id, users_table.value_2 - -> Incremental Sort - Sort Key: users_table.user_id, users_table.value_2 - Presorted Key: users_table.user_id - -> Index Scan using is_index1_1400256 on users_table_1400256 users_table -(22 rows) + -> Seq Scan on users_table_1400256 users_table +(17 rows) -- Grouping can be pushed down with aggregates even when window function can't +SELECT public.plan_without_result_lines($Q$ 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 +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Limit -> WindowAgg diff --git a/src/test/regress/expected/window_functions_0.out b/src/test/regress/expected/window_functions_0.out index 0a41bc0cc..c9442c7b5 100644 --- a/src/test/regress/expected/window_functions_0.out +++ b/src/test/regress/expected/window_functions_0.out @@ -1,6 +1,11 @@ +-- +-- 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 @@ -1490,10 +1495,12 @@ LIMIT 5; (18 rows) -- Grouping can be pushed down with aggregates even when window function can't +SELECT public.plan_without_result_lines($Q$ 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 +$Q$); + plan_without_result_lines --------------------------------------------------------------------- Limit -> WindowAgg diff --git a/src/test/regress/expected/window_functions_1.out b/src/test/regress/expected/window_functions_1.out deleted file mode 100644 index aea319c0b..000000000 --- a/src/test/regress/expected/window_functions_1.out +++ /dev/null @@ -1,1648 +0,0 @@ --- =================================================================== --- test top level window functions that are pushdownable --- =================================================================== --- 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 - -> Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - -> 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 -(17 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 - -> Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - -> 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 -(17 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 - -> Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - -> 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 -(17 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 - -> Sort - Sort Key: users_table.user_id, (avg(users_table.value_1)) DESC - -> 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 -(17 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/worker_split_binary_copy_test.out b/src/test/regress/expected/worker_split_binary_copy_test.out index 07dacbdb1..f23dc2043 100644 --- a/src/test/regress/expected/worker_split_binary_copy_test.out +++ b/src/test/regress/expected/worker_split_binary_copy_test.out @@ -186,6 +186,7 @@ SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \ SET citus.enable_binary_protocol = true; SELECT * from worker_split_copy( 81060000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81060015, -- destination shard id @@ -208,6 +209,7 @@ SELECT * from worker_split_copy( -- BEGIN: Trigger 2-way remote shard split copy. SELECT * from worker_split_copy( 81060000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81060015, -- destination shard id diff --git a/src/test/regress/expected/worker_split_copy_test.out b/src/test/regress/expected/worker_split_copy_test.out index c17ef5aa4..67d515198 100644 --- a/src/test/regress/expected/worker_split_copy_test.out +++ b/src/test/regress/expected/worker_split_copy_test.out @@ -54,6 +54,7 @@ SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \ -- BEGIN: Test Negative scenario SELECT * from worker_split_copy( 101, -- Invalid source shard id. + 'id', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id @@ -70,29 +71,34 @@ SELECT * from worker_split_copy( ERROR: could not find valid entry for shard xxxxx SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[] -- empty array ); ERROR: cannot determine type of empty array HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[]. SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[NULL] -- empty array ); -ERROR: function worker_split_copy(integer, text[]) does not exist +ERROR: function worker_split_copy(integer, unknown, text[]) does not exist HINT: No function matches the given name and argument types. You might need to add explicit type casts. SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[NULL::pg_catalog.split_copy_info]-- empty array ); ERROR: pg_catalog.split_copy_info array cannot contain null values SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[ROW(NULL)]-- empty array ); -ERROR: function worker_split_copy(integer, record[]) does not exist +ERROR: function worker_split_copy(integer, unknown, record[]) does not exist HINT: No function matches the given name and argument types. You might need to add explicit type casts. SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[ROW(NULL, NULL, NULL, NULL)::pg_catalog.split_copy_info] -- empty array ); ERROR: destination_shard_id for pg_catalog.split_copy_info cannot be null. @@ -102,6 +108,7 @@ ERROR: destination_shard_id for pg_catalog.split_copy_info cannot be null. SET citus.enable_binary_protocol = false; SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id diff --git a/src/test/regress/expected/worker_split_text_copy_test.out b/src/test/regress/expected/worker_split_text_copy_test.out index 164d3a6d7..cddb6d85e 100644 --- a/src/test/regress/expected/worker_split_text_copy_test.out +++ b/src/test/regress/expected/worker_split_text_copy_test.out @@ -149,6 +149,7 @@ SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \ SET citus.enable_binary_protocol = false; SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id @@ -171,6 +172,7 @@ SELECT * from worker_split_copy( -- BEGIN: Trigger 2-way remote shard split copy. SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id diff --git a/src/test/regress/failure_schedule b/src/test/regress/failure_schedule index 18a45fd26..816f9d9e2 100644 --- a/src/test/regress/failure_schedule +++ b/src/test/regress/failure_schedule @@ -18,6 +18,7 @@ test: failure_copy_on_hash test: failure_create_reference_table test: failure_create_distributed_table_non_empty test: failure_create_table +test: failure_create_distributed_table_concurrently test: failure_single_select test: failure_multi_shard_update_delete diff --git a/src/test/regress/isolation_schedule b/src/test/regress/isolation_schedule index 3ccc1e703..853da116e 100644 --- a/src/test/regress/isolation_schedule +++ b/src/test/regress/isolation_schedule @@ -26,12 +26,16 @@ test: isolation_citus_dist_activity test: isolation_remove_coordinator test: isolation_insert_select_repartition -test: isolation_dml_vs_repair isolation_copy_placement_vs_copy_placement +test: isolation_dml_vs_repair +test: isolation_copy_placement_vs_copy_placement test: isolation_concurrent_dml test: isolation_data_migration -test: isolation_drop_shards isolation_copy_placement_vs_modification -test: isolation_insert_vs_vacuum isolation_transaction_recovery isolation_vacuum_skip_locked +test: isolation_drop_shards +test: isolation_copy_placement_vs_modification +test: isolation_insert_vs_vacuum +test: isolation_transaction_recovery +test: isolation_vacuum_skip_locked test: isolation_progress_monitoring test: isolation_dump_local_wait_edges @@ -44,6 +48,7 @@ test: isolation_create_citus_local_table test: isolation_create_restore_point test: isolation_create_distributed_table +test: isolation_create_distributed_table_concurrently test: isolation_multi_shard_modify_vs_all test: isolation_modify_with_subquery_vs_dml test: isolation_hash_copy_vs_all diff --git a/src/test/regress/json_table_select_only.out b/src/test/regress/json_table_select_only.out new file mode 100644 index 000000000..61a120202 --- /dev/null +++ b/src/test/regress/json_table_select_only.out @@ -0,0 +1,1572 @@ +-- +-- PG15+ test +-- +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 +SET search_path TO "json table"; +-- insert some data +INSERT INTO jsonb_table_test (js) +VALUES ( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "d": [], "c": []}, + {"a": 2, "d": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "d": [1, 2], "c": []}, + {"x": "4", "d": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [100, 200, 300], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": [null]}, + {"x": "4", "b": [1, 2], "c": 2} + ]' +), +( + '[ + {"y": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "t": [1, 2], "c": []}, + {"x": "4", "b": [1, 200], "c": 96} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "100", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"t": 1, "b": [], "c": []}, + {"t": 2, "b": [1, 2, 3], "x": [10, null, 20]}, + {"t": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"U": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1000, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "T": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"ffa": 1, "b": [], "c": []}, + {"ffb": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"fffc": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt ORDER BY 1,2,3,4; + n | a | c | b +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 10 | + 2 | -1 | 20 | + 2 | -1 | | 1 + 2 | -1 | | 1 + 2 | -1 | | 2 + 2 | -1 | | 2 + 2 | -1 | | 3 + 2 | -1 | | 3 + 2 | -1 | | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 10 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | 20 | + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 1 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 2 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 3 + 2 | 2 | | 100 + 2 | 2 | | 200 + 2 | 2 | | 300 + 2 | 2 | | 1000 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | | 1 + 3 | -1 | | 1 + 3 | -1 | | 2 + 3 | -1 | | 2 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 1 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | 2 + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 1 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 2 + 4 | -1 | | 200 + 4 | -1 | | + 4 | -1 | | +(123 rows) + +-- default plan (inner, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (inner) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | + 2 | -1 | 1 | + 2 | -1 | 2 | + 2 | -1 | 2 | + 2 | -1 | 3 | + 2 | -1 | 3 | + 2 | -1 | | 10 + 2 | -1 | | 20 + 2 | -1 | | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | + 2 | 2 | 200 | + 2 | 2 | 300 | + 2 | 2 | 1000 | + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 10 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | 20 + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 2 | 2 | | + 3 | -1 | 1 | + 3 | -1 | 1 | + 3 | -1 | 2 | + 3 | -1 | 2 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | 2 | + 3 | 3 | | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 1 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 2 | + 4 | -1 | 200 | +(107 rows) + +-- default plan (inner, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 3 | 3 | 1 | + 3 | 3 | 2 | +(92 rows) + +-- default plan (outer, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 rows) + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + n | a | b | c +--------------------------------------------------------------------- + 1 | -1 | | + 1 | -1 | | + 1 | -1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 1 | 1 | | + 2 | -1 | 1 | 10 + 2 | -1 | 1 | 20 + 2 | -1 | 1 | + 2 | -1 | 2 | 10 + 2 | -1 | 2 | 20 + 2 | -1 | 2 | + 2 | -1 | 3 | 10 + 2 | -1 | 3 | 20 + 2 | -1 | 3 | + 2 | -1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 10 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | 20 + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 1 | + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | 20 + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 2 | + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | 20 + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 3 | + 2 | 2 | 100 | 10 + 2 | 2 | 100 | 20 + 2 | 2 | 100 | + 2 | 2 | 200 | 10 + 2 | 2 | 200 | 20 + 2 | 2 | 200 | + 2 | 2 | 300 | 10 + 2 | 2 | 300 | 20 + 2 | 2 | 300 | + 2 | 2 | 1000 | 10 + 2 | 2 | 1000 | 20 + 2 | 2 | 1000 | + 2 | 2 | | + 3 | -1 | | + 3 | -1 | | + 3 | 3 | 1 | + 3 | 3 | 2 | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 3 | 3 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | + 4 | -1 | | +(129 rows) + +select + jt.*, b1 + 100 as b +from + json_table (jsonb + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt ORDER BY 1,2,3,4,5; + n | a | b | b1 | c | c1 | b +--------------------------------------------------------------------- + 1 | 1 | [1, 10] | 1 | 1 | | 101 + 1 | 1 | [1, 10] | 1 | 2 | | 101 + 1 | 1 | [1, 10] | 1 | null | | 101 + 1 | 1 | [1, 10] | 10 | 1 | | 110 + 1 | 1 | [1, 10] | 10 | 2 | | 110 + 1 | 1 | [1, 10] | 10 | null | | 110 + 1 | 1 | [2] | 2 | 1 | | 102 + 1 | 1 | [2] | 2 | 2 | | 102 + 1 | 1 | [2] | 2 | null | | 102 + 1 | 1 | [3, 30, 300] | 3 | 1 | | 103 + 1 | 1 | [3, 30, 300] | 3 | 2 | | 103 + 1 | 1 | [3, 30, 300] | 3 | null | | 103 + 1 | 1 | [3, 30, 300] | 30 | 1 | | 130 + 1 | 1 | [3, 30, 300] | 30 | 2 | | 130 + 1 | 1 | [3, 30, 300] | 30 | null | | 130 + 1 | 1 | [3, 30, 300] | 300 | 1 | | 400 + 1 | 1 | [3, 30, 300] | 300 | 2 | | 400 + 1 | 1 | [3, 30, 300] | 300 | null | | 400 + 2 | 2 | | | | | + 3 | | | | | | +(20 rows) + +-- Should succeed (JSON arguments are passed to root and nested paths) +SELECT * +FROM + generate_series(1, 4) x, + generate_series(1, 3) y, + JSON_TABLE(jsonb + '[[1,2,3],[2,3,4,5],[3,4,5,6]]', + 'strict $[*] ? (@[*] < $x)' + PASSING x AS x, y AS y + COLUMNS ( + y text FORMAT JSON PATH '$', + NESTED PATH 'strict $[*] ? (@ >= $y)' + COLUMNS ( + z int PATH '$' + ) + ) + ) jt ORDER BY 4,1,2,3; + x | y | y | z +--------------------------------------------------------------------- + 2 | 1 | [1, 2, 3] | 1 + 3 | 1 | [1, 2, 3] | 1 + 4 | 1 | [1, 2, 3] | 1 + 2 | 1 | [1, 2, 3] | 2 + 2 | 2 | [1, 2, 3] | 2 + 3 | 1 | [1, 2, 3] | 2 + 3 | 1 | [2, 3, 4, 5] | 2 + 3 | 2 | [1, 2, 3] | 2 + 3 | 2 | [2, 3, 4, 5] | 2 + 4 | 1 | [1, 2, 3] | 2 + 4 | 1 | [2, 3, 4, 5] | 2 + 4 | 2 | [1, 2, 3] | 2 + 4 | 2 | [2, 3, 4, 5] | 2 + 2 | 1 | [1, 2, 3] | 3 + 2 | 2 | [1, 2, 3] | 3 + 2 | 3 | [1, 2, 3] | 3 + 3 | 1 | [1, 2, 3] | 3 + 3 | 1 | [2, 3, 4, 5] | 3 + 3 | 2 | [1, 2, 3] | 3 + 3 | 2 | [2, 3, 4, 5] | 3 + 3 | 3 | [1, 2, 3] | 3 + 3 | 3 | [2, 3, 4, 5] | 3 + 4 | 1 | [1, 2, 3] | 3 + 4 | 1 | [2, 3, 4, 5] | 3 + 4 | 1 | [3, 4, 5, 6] | 3 + 4 | 2 | [1, 2, 3] | 3 + 4 | 2 | [2, 3, 4, 5] | 3 + 4 | 2 | [3, 4, 5, 6] | 3 + 4 | 3 | [1, 2, 3] | 3 + 4 | 3 | [2, 3, 4, 5] | 3 + 4 | 3 | [3, 4, 5, 6] | 3 + 3 | 1 | [2, 3, 4, 5] | 4 + 3 | 2 | [2, 3, 4, 5] | 4 + 3 | 3 | [2, 3, 4, 5] | 4 + 4 | 1 | [2, 3, 4, 5] | 4 + 4 | 1 | [3, 4, 5, 6] | 4 + 4 | 2 | [2, 3, 4, 5] | 4 + 4 | 2 | [3, 4, 5, 6] | 4 + 4 | 3 | [2, 3, 4, 5] | 4 + 4 | 3 | [3, 4, 5, 6] | 4 + 3 | 1 | [2, 3, 4, 5] | 5 + 3 | 2 | [2, 3, 4, 5] | 5 + 3 | 3 | [2, 3, 4, 5] | 5 + 4 | 1 | [2, 3, 4, 5] | 5 + 4 | 1 | [3, 4, 5, 6] | 5 + 4 | 2 | [2, 3, 4, 5] | 5 + 4 | 2 | [3, 4, 5, 6] | 5 + 4 | 3 | [2, 3, 4, 5] | 5 + 4 | 3 | [3, 4, 5, 6] | 5 + 4 | 1 | [3, 4, 5, 6] | 6 + 4 | 2 | [3, 4, 5, 6] | 6 + 4 | 3 | [3, 4, 5, 6] | 6 +(52 rows) + diff --git a/src/test/regress/json_table_select_only_0.out b/src/test/regress/json_table_select_only_0.out new file mode 100644 index 000000000..c04e76814 --- /dev/null +++ b/src/test/regress/json_table_select_only_0.out @@ -0,0 +1,9 @@ +-- +-- PG15+ test +-- +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/mitmscripts/fluent.py b/src/test/regress/mitmscripts/fluent.py index 2fc408e03..2e6be9d34 100644 --- a/src/test/regress/mitmscripts/fluent.py +++ b/src/test/regress/mitmscripts/fluent.py @@ -114,8 +114,8 @@ class ActionsMixin: self.next = CancelHandler(self.root, pid) return self.next - def delay(self, timeMs): - self.next = DelayHandler(self.root, timeMs) + def connect_delay(self, timeMs): + self.next = ConnectDelayHandler(self.root, timeMs) return self.next class AcceptHandler(Handler): @@ -174,13 +174,14 @@ class CancelHandler(Handler): time.sleep(0.1) return 'done' -class DelayHandler(Handler): - 'Delay a packet by sleeping before deciding what to do' +class ConnectDelayHandler(Handler): + 'Delay the initial packet by sleeping before deciding what to do' def __init__(self, root, timeMs): super().__init__(root) self.timeMs = timeMs def _handle(self, flow, message): - time.sleep(self.timeMs/1000.0) + if message.is_initial: + time.sleep(self.timeMs/1000.0) return 'done' class Contains(Handler, ActionsMixin, FilterableMixin): diff --git a/src/test/regress/multi_1_schedule b/src/test/regress/multi_1_schedule index 433254dec..9b248b5cd 100644 --- a/src/test/regress/multi_1_schedule +++ b/src/test/regress/multi_1_schedule @@ -62,7 +62,7 @@ test: multi_remove_node_reference_table # ---------- 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_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_shard_update_delete recursive_dml_with_different_planners_executors test: insert_select_repartition window_functions dml_recursive multi_insert_select_window @@ -277,7 +277,7 @@ test: add_coordinator test: replicate_reference_tables_to_coordinator test: citus_local_tables test: mixed_relkind_tests -test: multi_row_router_insert +test: multi_row_router_insert create_distributed_table_concurrently test: multi_reference_table citus_local_tables_queries test: citus_local_table_triggers test: coordinator_shouldhaveshards diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index ab8d092ab..a92da42fd 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -55,9 +55,10 @@ test: subquery_in_targetlist subquery_in_where subquery_complex_target_list subq test: subquery_prepared_statements test: non_colocated_leaf_subquery_joins non_colocated_subquery_joins test: cte_inline recursive_view_local_table values sequences_with_different_types -test: pg13 pg12 +test: pg13 pg12 pg15_json json_table_select_only # run pg14 sequentially as it syncs metadata test: pg14 +test: pg15 test: drop_column_partitioned_table test: tableam diff --git a/src/test/regress/pg_regress_multi.pl b/src/test/regress/pg_regress_multi.pl index 4bceecd7f..32db65a5c 100755 --- a/src/test/regress/pg_regress_multi.pl +++ b/src/test/regress/pg_regress_multi.pl @@ -451,12 +451,12 @@ push(@pgOptions, "wal_level='logical'"); # Faster logical replication status update so tests with logical replication # run faster -push(@pgOptions, "wal_receiver_status_interval=1"); +push(@pgOptions, "wal_receiver_status_interval=0"); # Faster logical replication apply worker launch so tests with logical # replication run faster. This is used in ApplyLauncherMain in # src/backend/replication/logical/launcher.c. -push(@pgOptions, "wal_retrieve_retry_interval=1000"); +push(@pgOptions, "wal_retrieve_retry_interval=250"); push(@pgOptions, "max_logical_replication_workers=50"); push(@pgOptions, "max_wal_senders=50"); @@ -704,7 +704,7 @@ if (!$conninfo) # Create new data directories, copy workers for speed # --allow-group-access is used to ensure we set permissions on private keys # correctly - system(catfile("$bindir", "initdb"), ("--no-sync", "--allow-group-access", "-U", $user, "--encoding", "UTF8", catfile($TMP_CHECKDIR, $MASTERDIR, "data"))) == 0 + system(catfile("$bindir", "initdb"), ("--no-sync", "--allow-group-access", "-U", $user, "--encoding", "UTF8", "--locale", "POSIX", catfile($TMP_CHECKDIR, $MASTERDIR, "data"))) == 0 or die "Could not create $MASTERDIR data directory"; generate_hba("master"); @@ -789,18 +789,17 @@ if ($useMitmproxy) die "a file already exists at $mitmFifoPath, delete it before trying again"; } - system("lsof -i :$mitmPort"); - if (! $?) { - die "cannot start mitmproxy because a process already exists on port $mitmPort"; - } - if ($Config{osname} eq "linux") { - system("netstat --tcp -n | grep $mitmPort"); + system("netstat --tcp -n | grep :$mitmPort"); } else { - system("netstat -p tcp -n | grep $mitmPort"); + system("netstat -p tcp -n | grep :$mitmPort"); + } + + if (system("lsof -i :$mitmPort") == 0) { + die "cannot start mitmproxy because a process already exists on port $mitmPort"; } my $childPid = fork(); @@ -1102,6 +1101,18 @@ sub RunVanillaTests ("--dbname", "$dbName")); } +if ($useMitmproxy) { + my $tries = 0; + until(system("lsof -i :$mitmPort") == 0) { + if ($tries > 60) { + die("waited for 60 seconds to start the mitmproxy, but it failed") + } + print("waiting: mitmproxy was not started yet\n"); + sleep(1); + $tries++; + } +} + # Finally run the tests if ($vanillatest) { diff --git a/src/test/regress/spec/isolation_create_distributed_table_concurrently.spec b/src/test/regress/spec/isolation_create_distributed_table_concurrently.spec new file mode 100644 index 000000000..02c31c96b --- /dev/null +++ b/src/test/regress/spec/isolation_create_distributed_table_concurrently.spec @@ -0,0 +1,221 @@ +setup +{ + -- make sure coordinator is in metadata + SELECT citus_set_coordinator_host('localhost', 57636); + CREATE TABLE table_1(id int PRIMARY KEY); + CREATE TABLE table_2(id smallint PRIMARY KEY); + CREATE TABLE table_default_colocated(id int PRIMARY KEY); + CREATE TABLE table_none_colocated(id int PRIMARY KEY); +} + +teardown +{ + DROP TABLE table_1 CASCADE; + DROP TABLE table_2 CASCADE; + DROP TABLE table_default_colocated CASCADE; + DROP TABLE table_none_colocated CASCADE; + SELECT citus_remove_node('localhost', 57636); +} + +session "s1" + +step "s1-create-concurrently-table_1" +{ + SELECT create_distributed_table_concurrently('table_1', 'id'); +} + +step "s1-create-concurrently-table_2" +{ + SELECT create_distributed_table_concurrently('table_2', 'id'); +} + +step "s1-create-concurrently-table_default_colocated" +{ + SELECT create_distributed_table_concurrently('table_default_colocated', 'id'); +} + +step "s1-create-concurrently-table_none_colocated" +{ + SELECT create_distributed_table_concurrently('table_none_colocated', 'id', colocate_with => 'none'); +} + +step "s1-settings" +{ + -- session needs to have replication factor set to 1, can't do in setup + SET citus.shard_count TO 4; + SET citus.shard_replication_factor TO 1; +} + +step "s1-truncate" +{ + TRUNCATE table_1; +} + +session "s2" + +step "s2-begin" +{ + BEGIN; +} + +step "s2-settings" +{ + -- session needs to have replication factor set to 1, can't do in setup + SET citus.shard_count TO 4; + SET citus.shard_replication_factor TO 1; +} + +step "s2-insert" +{ + INSERT INTO table_1 SELECT s FROM generate_series(1,20) s; +} + +step "s2-update" +{ + UPDATE table_1 SET id = 21 WHERE id = 20; +} + +step "s2-delete" +{ + DELETE FROM table_1 WHERE id = 11; +} + +step "s2-copy" +{ + COPY table_1 FROM PROGRAM 'echo 30 && echo 31 && echo 32 && echo 33 && echo 34 && echo 35 && echo 36 && echo 37 && echo 38'; +} + +step "s2-reindex" +{ + REINDEX TABLE table_1; +} + +step "s2-reindex-concurrently" +{ + REINDEX TABLE CONCURRENTLY table_1; +} + +step "s2-create-concurrently-table_1" +{ + SELECT create_distributed_table_concurrently('table_1', 'id'); +} + +step "s2-create-table_1" +{ + SELECT create_distributed_table('table_1', 'id'); +} + +step "s2-create-concurrently-table_2" +{ + SELECT create_distributed_table_concurrently('table_2', 'id'); +} + +step "s2-create-table_2" +{ + SELECT create_distributed_table('table_2', 'id'); +} + +step "s2-create-table_2-none" +{ + SELECT create_distributed_table('table_2', 'id', colocate_with => 'none'); +} + +step "s2-print-status" +{ + -- sanity check on partitions + SELECT * FROM pg_dist_shard + WHERE logicalrelid = 'table_1'::regclass OR logicalrelid = 'table_2'::regclass + ORDER BY shardminvalue::BIGINT, logicalrelid; + + -- sanity check on total elements in the table + SELECT COUNT(*) FROM table_1; +} + +step "s2-commit" +{ + COMMIT; +} + +session "s3" + +// this advisory lock with (almost) random values are only used +// for testing purposes. For details, check Citus' logical replication +// source code +step "s3-acquire-split-advisory-lock" +{ + SELECT pg_advisory_lock(44000, 55152); +} + +step "s3-release-split-advisory-lock" +{ + SELECT pg_advisory_unlock(44000, 55152); +} + +session "s4" + +step "s4-print-waiting-locks" +{ + SELECT mode, relation::regclass, granted FROM pg_locks + WHERE relation = 'table_1'::regclass OR relation = 'table_2'::regclass + ORDER BY mode, relation, granted; +} + +step "s4-print-waiting-advisory-locks" +{ + SELECT mode, classid, objid, objsubid, granted FROM pg_locks + WHERE locktype = 'advisory' AND classid = 0 AND objid = 3 AND objsubid = 9 + ORDER BY granted; +} + +step "s4-print-colocations" +{ + SELECT * FROM pg_dist_colocation ORDER BY colocationid; +} + +// show concurrent insert is NOT blocked by create_distributed_table_concurrently +permutation "s1-truncate" "s3-acquire-split-advisory-lock" "s1-settings" "s2-settings" "s1-create-concurrently-table_1" "s2-begin" "s2-insert" "s2-commit" "s3-release-split-advisory-lock" "s2-print-status" + +// show concurrent update is NOT blocked by create_distributed_table_concurrently +permutation "s1-truncate" "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-begin" "s2-insert" "s2-update" "s2-commit" "s3-release-split-advisory-lock" "s2-print-status" + +// show concurrent delete is NOT blocked by create_distributed_table_concurrently +permutation "s1-truncate" "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-begin" "s2-insert" "s2-delete" "s2-commit" "s3-release-split-advisory-lock" "s2-print-status" + +// show concurrent copy is NOT blocked by create_distributed_table_concurrently +permutation "s1-truncate" "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-begin" "s2-insert" "s2-copy" "s2-commit" "s3-release-split-advisory-lock" "s2-print-status" + +// show concurrent reindex concurrently is blocked by create_distributed_table_concurrently +// both tries to acquire SHARE UPDATE EXCLUSIVE on the table +permutation "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-insert" "s2-reindex-concurrently" "s4-print-waiting-locks" "s3-release-split-advisory-lock" + +// show concurrent reindex is blocked by create_distributed_table_concurrently +// reindex tries to acquire ACCESS EXCLUSIVE lock while create-concurrently tries to acquire SHARE UPDATE EXCLUSIVE on the table +permutation "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-insert" "s2-reindex" "s4-print-waiting-locks" "s3-release-split-advisory-lock" + +// show create_distributed_table_concurrently operation inside a transaction are NOT allowed +permutation "s2-begin" "s2-create-concurrently-table_1" "s2-commit" + +// show concurrent create_distributed_table_concurrently operations with the same table are NOT allowed +permutation "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-create-concurrently-table_1" "s3-release-split-advisory-lock" + +// show concurrent create_distributed_table_concurrently operations with different tables are NOT allowed +permutation "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-create-concurrently-table_2" "s3-release-split-advisory-lock" + +// show concurrent create_distributed_table_concurrently and create_distribute_table operations with the same table are NOT allowed +permutation "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-create-table_1" "s3-release-split-advisory-lock" + +// show concurrent create_distributed_table_concurrently and create_distribute_table operations with different tables are allowed +permutation "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-create-table_2" "s3-release-split-advisory-lock" + +// tests with colocated_with combinations +// show concurrent colocate_with => 'default' and colocate_with => 'default' are NOT allowed if there is no default colocation entry yet. +permutation "s2-begin" "s2-create-table_2" "s1-create-concurrently-table_default_colocated" "s4-print-waiting-advisory-locks" "s2-commit" "s4-print-colocations" + +// show concurrent colocate_with => 'default' and colocate_with => 'default' are allowed if there is already a default colocation entry. +permutation "s1-create-concurrently-table_default_colocated" "s3-acquire-split-advisory-lock" "s1-create-concurrently-table_1" "s2-create-table_2" "s4-print-waiting-advisory-locks" "s3-release-split-advisory-lock" "s4-print-colocations" + +// show concurrent colocate_with => 'default' and colocate_with => 'none' are allowed. +permutation "s2-begin" "s2-create-table_2" "s1-create-concurrently-table_none_colocated" "s4-print-waiting-advisory-locks" "s2-commit" "s4-print-colocations" + +// show concurrent colocate_with => 'none' and colocate_with => 'none' are allowed. +permutation "s2-begin" "s2-create-table_2-none" "s1-create-concurrently-table_none_colocated" "s4-print-waiting-advisory-locks" "s2-commit" "s4-print-colocations" diff --git a/src/test/regress/spec/isolation_distributed_deadlock_detection.spec b/src/test/regress/spec/isolation_distributed_deadlock_detection.spec index 19526453c..4667082e6 100644 --- a/src/test/regress/spec/isolation_distributed_deadlock_detection.spec +++ b/src/test/regress/spec/isolation_distributed_deadlock_detection.spec @@ -306,49 +306,49 @@ step "deadlock-checker-call" } // simplest case, loop with two nodes (Reminder: Citus uses 2PC) -permutation "s1-begin" "s2-begin" "s1-update-1" "s2-update-2" "s2-update-1" "deadlock-checker-call" "s1-update-2" "deadlock-checker-call" "s1-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s1-update-1" "s2-update-2" "s2-update-1" "deadlock-checker-call" "s1-update-2"("s2-update-1") "deadlock-checker-call" "s1-commit" "s2-commit" // simplest case with replication factor 2 -permutation "s1-begin" "s2-begin" "s1-update-1-rep-2" "s2-update-2-rep-2" "s2-update-1-rep-2" "deadlock-checker-call" "s1-update-2-rep-2" "deadlock-checker-call" "s1-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s1-update-1-rep-2" "s2-update-2-rep-2" "s2-update-1-rep-2" "deadlock-checker-call" "s1-update-2-rep-2"("s2-update-1-rep-2") "deadlock-checker-call" "s1-commit" "s2-commit" // simplest case with multi-shard query is cancelled -permutation "s1-begin" "s2-begin" "s1-update-1" "s2-update-2" "s1-update-2" "deadlock-checker-call" "s2-upsert-select-all" "deadlock-checker-call" "s1-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s1-update-1" "s2-update-2" "s1-update-2" "deadlock-checker-call" "s2-upsert-select-all"("s1-update-2") "deadlock-checker-call" "s2-commit" "s1-commit" // simplest case with DDL is cancelled -permutation "s1-begin" "s2-begin" "s1-update-1" "s2-update-2" "s1-update-2" "deadlock-checker-call" "s2-ddl" "deadlock-checker-call" "s1-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s1-update-1" "s2-update-2" "s1-update-2" "deadlock-checker-call" "s2-ddl"("s1-update-2") "deadlock-checker-call" "s2-commit" "s1-commit" // daedlock with local table -permutation "s1-begin" "s2-begin" "s1-insert-dist-10" "s2-insert-local-10" "s2-insert-dist-10" "s1-insert-local-10" "deadlock-checker-call" "s1-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s1-insert-dist-10" "s2-insert-local-10" "s2-insert-dist-10" "s1-insert-local-10"("s2-insert-dist-10") "deadlock-checker-call" "s1-commit" "s2-commit" // daedlock with reference tables only -permutation "s1-begin" "s2-begin" "s2-insert-ref-10" "s1-insert-ref-11" "s1-insert-ref-10" "s2-insert-ref-11" "deadlock-checker-call" "s1-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s2-insert-ref-10" "s1-insert-ref-11" "s1-insert-ref-10" "s2-insert-ref-11"("s1-insert-ref-10") "deadlock-checker-call" "s2-commit" "s1-commit" // deadlock with referecen + distributed tables -permutation "s1-begin" "s2-begin" "s2-insert-ref-10" "s1-update-1" "deadlock-checker-call" "s2-update-1" "s1-insert-ref-10" "deadlock-checker-call" "s1-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s2-insert-ref-10" "s1-update-1" "deadlock-checker-call" "s2-update-1" "s1-insert-ref-10"("s2-update-1") "deadlock-checker-call" "s1-commit" "s2-commit" // slightly more complex case, loop with three nodes -permutation "s1-begin" "s2-begin" "s3-begin" "s1-update-1" "s2-update-2" "s3-update-3" "deadlock-checker-call" "s1-update-2" "s2-update-3" "s3-update-1" "deadlock-checker-call" "s3-commit" "s2-commit" "s1-commit" +permutation "s1-begin" "s2-begin" "s3-begin" "s1-update-1" "s2-update-2" "s3-update-3" "deadlock-checker-call" "s1-update-2" "s2-update-3" "s3-update-1"("s2-update-3") "deadlock-checker-call" "s3-commit" "s2-commit" "s1-commit" // similar to the above (i.e., 3 nodes), but the cycle starts from the second node -permutation "s1-begin" "s2-begin" "s3-begin" "s2-update-1" "s1-update-1" "s2-update-2" "s3-update-3" "s3-update-2" "deadlock-checker-call" "s2-update-3" "deadlock-checker-call" "s3-commit" "s2-commit" "s1-commit" +permutation "s1-begin" "s2-begin" "s3-begin" "s2-update-1" "s1-update-1" "s2-update-2" "s3-update-3" "s3-update-2" "deadlock-checker-call" "s2-update-3"("s3-update-2") "deadlock-checker-call" "s2-commit" "s3-commit" "s1-commit" // not connected graph -permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s1-update-1" "s2-update-2" "s3-update-3" "s3-update-2" "deadlock-checker-call" "s4-update-4" "s2-update-3" "deadlock-checker-call" "s3-commit" "s2-commit" "s1-commit" "s4-commit" +permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s1-update-1" "s2-update-2" "s3-update-3" "s3-update-2" "deadlock-checker-call" "s4-update-4" "s2-update-3"("s3-update-2") "deadlock-checker-call" "s2-commit" "s3-commit" "s1-commit" "s4-commit" // still a not connected graph, but each smaller graph contains dependencies, one of which is a distributed deadlock -permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s4-update-1" "s1-update-1" "deadlock-checker-call" "s2-update-2" "s3-update-3" "s2-update-3" "s3-update-2" "deadlock-checker-call" "s3-commit" "s2-commit" "s4-commit" "s1-commit" +permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s4-update-1" "s1-update-1" "deadlock-checker-call" "s2-update-2" "s3-update-3" "s2-update-3" "s3-update-2"("s2-update-3") "deadlock-checker-call" "s3-commit" "s2-commit" "s4-commit" "s1-commit" // multiple deadlocks on a not connected graph -permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s1-update-1" "s4-update-4" "s2-update-2" "s3-update-3" "s3-update-2" "s4-update-1" "s1-update-4" "deadlock-checker-call" "s1-commit" "s4-commit" "s2-update-3" "deadlock-checker-call" "s2-commit" "s3-commit" +permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s1-update-1" "s4-update-4" "s2-update-2" "s3-update-3" "s3-update-2" "s4-update-1" "s1-update-4"("s4-update-1") "deadlock-checker-call" "s1-commit" "s4-commit" "s2-update-3"("s3-update-2") "deadlock-checker-call" "s2-commit" "s3-commit" // a larger graph where the first node is in the distributed deadlock -permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s5-begin" "s6-begin" "s1-update-1" "s5-update-5" "s3-update-2" "s2-update-3" "s4-update-4" "s3-update-4" "deadlock-checker-call" "s6-update-6" "s4-update-6" "s1-update-5" "s5-update-1" "deadlock-checker-call" "s1-commit" "s5-commit" "s6-commit" "s4-commit" "s3-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s5-begin" "s6-begin" "s1-update-1" "s5-update-5" "s3-update-2" "s2-update-3" "s4-update-4" "s3-update-4" "deadlock-checker-call" "s6-update-6" "s4-update-6" "s1-update-5" "s5-update-1"("s1-update-5") "deadlock-checker-call" "s5-commit" "s1-commit" "s6-commit" "s4-commit" "s3-commit" "s2-commit" // a larger graph where the deadlock starts from a middle node -permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s5-begin" "s6-begin" "s6-update-6" "s5-update-5" "s5-update-6" "s4-update-4" "s1-update-4" "s4-update-5" "deadlock-checker-call" "s2-update-3" "s3-update-2" "s2-update-2" "s3-update-3" "deadlock-checker-call" "s6-commit" "s5-commit" "s4-commit" "s1-commit" "s3-commit" "s2-commit" +permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s5-begin" "s6-begin" "s6-update-6" "s5-update-5" "s5-update-6" "s4-update-4" "s1-update-4" "s4-update-5" "deadlock-checker-call" "s2-update-3" "s3-update-2" "s2-update-2" "s3-update-3"("s2-update-2") "deadlock-checker-call" "s3-commit" "s6-commit" "s5-commit" "s4-commit" "s1-commit" "s2-commit" // a larger graph where the deadlock starts from the last node -permutation "s1-begin" "s2-begin" "s3-begin" "s4-begin" "s5-begin" "s6-begin" "s5-update-5" "s3-update-2" "s2-update-2" "s4-update-4" "s3-update-4" "s4-update-5" "s1-update-4" "deadlock-checker-call" "s6-update-6" "s5-update-6" "s6-update-5" "deadlock-checker-call" "s5-commit" "s6-commit" "s4-commit" "s3-commit" "s1-commit" "s2-commit" +permutation "s2-begin" "s3-begin" "s4-begin" "s5-begin" "s6-begin" "s5-update-5" "s3-update-2" "s2-update-2" "s4-update-4" "s3-update-4" "s4-update-5" "deadlock-checker-call" "s6-update-6" "s5-update-6" "s6-update-5"("s5-update-6") "deadlock-checker-call" "s6-commit" "s5-commit" "s4-commit" "s3-commit" "s2-commit" // a backend is blocked on multiple backends // note that session 5 is not strictly necessary to simulate the deadlock diff --git a/src/test/regress/spec/isolation_ensure_dependency_activate_node.spec b/src/test/regress/spec/isolation_ensure_dependency_activate_node.spec index 5891c153a..5b74d5643 100644 --- a/src/test/regress/spec/isolation_ensure_dependency_activate_node.spec +++ b/src/test/regress/spec/isolation_ensure_dependency_activate_node.spec @@ -18,6 +18,7 @@ teardown SELECT 1 FROM master_add_node('localhost', 57638); RESET search_path; + DROP SCHEMA IF EXISTS col_schema CASCADE; DROP TABLE IF EXISTS t1 CASCADE; DROP TABLE IF EXISTS t2 CASCADE; DROP TABLE IF EXISTS t3 CASCADE; @@ -118,6 +119,13 @@ step "s2-commit" COMMIT; } +step "s2-create-table-for-colocation" +{ + CREATE SCHEMA col_schema; + CREATE TABLE col_schema.col_tbl (a INT, b INT); + SELECT create_distributed_table('col_schema.col_tbl', 'a'); +} + // prints from session 2 are run at the end when the worker has already been added by the // test step "s2-print-distributed-objects" @@ -199,7 +207,7 @@ permutation "s1-print-distributed-objects" "s1-begin" "s2-begin" "s2-create-sche // concurrency tests with multi schema distribution permutation "s1-print-distributed-objects" "s2-create-schema" "s1-begin" "s2-begin" "s1-add-worker" "s2-create-table" "s1-commit" "s2-commit" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" -permutation "s1-print-distributed-objects" "s1-add-worker" "s2-create-schema" "s2-begin" "s3-begin" "s3-use-schema" "s2-create-table" "s3-create-table" "s2-commit" "s3-commit" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" +permutation "s1-print-distributed-objects" "s2-create-table-for-colocation" "s1-add-worker" "s2-create-schema" "s2-begin" "s3-begin" "s3-use-schema" "s2-create-table" "s3-create-table" "s2-commit" "s3-commit" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" permutation "s1-print-distributed-objects" "s1-begin" "s2-begin" "s3-begin" "s1-add-worker" "s2-create-schema" "s3-create-schema2" "s1-commit" "s2-create-table" "s2-commit" "s3-create-table" "s3-commit" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" // type and schema tests @@ -212,10 +220,10 @@ permutation "s1-print-distributed-objects" "s1-begin" "s2-begin" "s2-create-sche // s3-wait-for-metadata-sync step, we do "s2-begin" followed directly by // "s2-commit", because "COMMIT" syncs the messages -permutation "s1-print-distributed-objects" "s1-begin" "s1-add-worker" "s2-public-schema" "s2-distribute-function" "s1-commit" "s2-begin" "s2-commit" "s3-wait-for-metadata-sync" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" -permutation "s1-print-distributed-objects" "s1-begin" "s2-public-schema" "s2-distribute-function" "s2-begin" "s2-commit" "s3-wait-for-metadata-sync" "s1-add-worker" "s1-commit" "s3-wait-for-metadata-sync" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" +permutation "s1-print-distributed-objects" "s2-create-table-for-colocation" "s1-begin" "s1-add-worker" "s2-public-schema" "s2-distribute-function" "s1-commit" "s2-begin" "s2-commit" "s3-wait-for-metadata-sync" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" +permutation "s1-print-distributed-objects" "s2-create-table-for-colocation" "s1-begin" "s2-public-schema" "s2-distribute-function" "s2-begin" "s2-commit" "s3-wait-for-metadata-sync" "s1-add-worker" "s1-commit" "s3-wait-for-metadata-sync" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" // we cannot run the following operations concurrently // the problem is that NOTIFY event doesn't (reliably) happen before COMMIT // so we have to commit s2 before s1 starts -permutation "s1-print-distributed-objects" "s2-begin" "s2-create-schema" "s2-distribute-function" "s2-commit" "s3-wait-for-metadata-sync" "s1-begin" "s1-add-worker" "s1-commit" "s3-wait-for-metadata-sync" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" +permutation "s1-print-distributed-objects" "s2-create-table-for-colocation" "s2-begin" "s2-create-schema" "s2-distribute-function" "s2-commit" "s3-wait-for-metadata-sync" "s1-begin" "s1-add-worker" "s1-commit" "s3-wait-for-metadata-sync" "s2-print-distributed-objects" "s3-drop-coordinator-schemas" diff --git a/src/test/regress/spec/isolation_get_distributed_wait_queries_mx.spec b/src/test/regress/spec/isolation_get_distributed_wait_queries_mx.spec index 03512f343..1545845ec 100644 --- a/src/test/regress/spec/isolation_get_distributed_wait_queries_mx.spec +++ b/src/test/regress/spec/isolation_get_distributed_wait_queries_mx.spec @@ -275,7 +275,8 @@ permutation "s1-begin" "s1-update-ref-table-from-coordinator" "s2-start-session- // show that we can see blocking activity even if these are the first commands in the sessions // such that global_pids have not been assigned -// in the second permutation, s3-show-actual-gpids shows the gpid for ALTER TABLE -// because ALTER TABLE is not blocked on the parser but during the execution (hence gpid already asssigned) -"s5-begin" "s5-alter" "s6-select" "s3-select-distributed-waiting-queries" "s3-show-actual-gpids" "s5-rollback" -"s8-begin" "s8-select" "s7-alter" "s3-select-distributed-waiting-queries" "s3-show-actual-gpids" "s8-rollback" +// in the first permutation, we would normally have s3-show-actual-gpids and it'd show the gpid has NOT been assigned +// however, as of PG commit 3f323956128ff8589ce4d3a14e8b950837831803, isolation tester sends set application_name command +// even before we can do anything on the session. That's why we removed s3-show-actual-gpids, but still useful to show waiting queries +permutation "s5-begin" "s5-alter" "s6-select" "s3-select-distributed-waiting-queries" "s5-rollback" +permutation "s8-begin" "s8-select" "s7-alter" "s3-select-distributed-waiting-queries" "s3-show-actual-gpids" "s8-rollback" diff --git a/src/test/regress/spec/isolation_logical_replication_binaryless.spec b/src/test/regress/spec/isolation_logical_replication_binaryless.spec new file mode 100644 index 000000000..f89dac62e --- /dev/null +++ b/src/test/regress/spec/isolation_logical_replication_binaryless.spec @@ -0,0 +1,52 @@ +// This file tests that logical replication works even when the table that's +// being moved contains columns that don't allow for binary encoding +setup +{ + SET citus.shard_count TO 1; + SET citus.shard_replication_factor TO 1; + ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 45076800; + CREATE TABLE t_nonbinary(id bigserial, nonbinary aclitem); + SELECT create_distributed_table('t_nonbinary', 'id'); + INSERT INTO t_nonbinary (SELECT i, 'user postgres=r/postgres' FROM generate_series(1, 5) i); +} + +teardown +{ + DROP TABLE t_nonbinary; +} + + +session "s1" + +step "s1-move-placement" +{ + SELECT citus_move_shard_placement(45076800, 'localhost', 57637, 'localhost', 57638, shard_transfer_mode:='force_logical'); +} + +step "s1-select" +{ + SELECT * FROM t_nonbinary order by id; +} + +session "s2" +step "s2-insert" +{ + INSERT INTO t_nonbinary (SELECT i, 'user postgres=r/postgres' FROM generate_series(6, 10) i); +} + +session "s3" + +// this advisory lock with (almost) random values are only used +// for testing purposes. For details, check Citus' logical replication +// source code +step "s3-acquire-advisory-lock" +{ + SELECT pg_advisory_lock(44000, 55152); +} + +step "s3-release-advisory-lock" +{ + SELECT pg_advisory_unlock(44000, 55152); +} + +permutation "s3-acquire-advisory-lock" "s1-move-placement" "s2-insert" "s3-release-advisory-lock" "s1-select" diff --git a/src/test/regress/spec/isolation_ref2ref_foreign_keys.spec b/src/test/regress/spec/isolation_ref2ref_foreign_keys.spec index 432b67e76..4967883c0 100644 --- a/src/test/regress/spec/isolation_ref2ref_foreign_keys.spec +++ b/src/test/regress/spec/isolation_ref2ref_foreign_keys.spec @@ -1,5 +1,6 @@ setup { + ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 8429800; CREATE TABLE ref_table_1(id int PRIMARY KEY, value int); SELECT create_reference_table('ref_table_1'); @@ -73,11 +74,22 @@ step "s1-select-table-3" step "s1-view-locks" { - SELECT mode, count(*) - FROM pg_locks + -- The following output changed in PG versions 13.6 and 14.2. The output is expected + -- to change in earlier versions of PG as the and application_name format did not use + -- to include session name at the end. + SELECT classid, + objid, + objsubid, + mode, + application_name, + backend_type, + regexp_replace(query, E'[\\n\\r\\u2028]+', ' ', 'g' ) query + FROM pg_locks l + JOIN pg_stat_activity a + ON l.pid = a.pid WHERE locktype='advisory' - GROUP BY mode - ORDER BY 1, 2; + AND application_name <> 'Citus Maintenance Daemon' + ORDER BY 1, 2, 3, 4; } step "s1-rollback" diff --git a/src/test/regress/spec/isolation_ref_update_delete_upsert_vs_all_on_mx.spec b/src/test/regress/spec/isolation_ref_update_delete_upsert_vs_all_on_mx.spec index 049416c5d..b37bd5f9f 100644 --- a/src/test/regress/spec/isolation_ref_update_delete_upsert_vs_all_on_mx.spec +++ b/src/test/regress/spec/isolation_ref_update_delete_upsert_vs_all_on_mx.spec @@ -118,6 +118,6 @@ step "s3-select-count" permutation "s1-add-primary-key" "s1-start-session-level-connection" "s1-begin-on-worker" "s1-upsert" "s2-start-session-level-connection" "s2-begin-on-worker" "s2-select" "s1-commit-worker" "s2-commit-worker" "s1-stop-connection" "s2-stop-connection" "s3-select-count" permutation "s1-start-session-level-connection" "s1-begin-on-worker" "s1-delete" "s2-start-session-level-connection" "s2-begin-on-worker" "s2-insert-select-ref-table" "s1-commit-worker" "s2-commit-worker" "s1-stop-connection" "s2-stop-connection" "s3-select-count" -permutation "s1-add-primary-key" "s1-start-session-level-connection" "s1-begin-on-worker" "s1-upsert" "s2-start-session-level-connection" "s2-begin-on-worker" "s2-drop" "s1-commit-worker" "s2-commit-worker" "s1-stop-connection" "s2-stop-connection" "s3-select-count" +permutation "s1-add-primary-key" "s1-start-session-level-connection" "s1-begin-on-worker" "s1-upsert" "s2-drop" "s1-commit-worker" "s1-stop-connection" "s3-select-count" permutation "s1-start-session-level-connection" "s1-begin-on-worker" "s1-delete" "s2-start-session-level-connection" "s2-begin-on-worker" "s2-truncate" "s1-commit-worker" "s2-commit-worker" "s1-stop-connection" "s2-stop-connection" "s3-select-count" permutation "s1-start-session-level-connection" "s1-begin-on-worker" "s1-delete" "s2-coordinator-create-index-concurrently" "s1-commit-worker" "s2-empty" "s3-select-count" "s1-stop-connection" diff --git a/src/test/regress/spec/isolation_tenant_isolation_nonblocking.spec b/src/test/regress/spec/isolation_tenant_isolation_nonblocking.spec index a1356ed17..e7395e631 100644 --- a/src/test/regress/spec/isolation_tenant_isolation_nonblocking.spec +++ b/src/test/regress/spec/isolation_tenant_isolation_nonblocking.spec @@ -3,18 +3,23 @@ setup SET citus.shard_count to 2; SET citus.shard_replication_factor to 1; SELECT setval('pg_dist_shardid_seq', - CASE WHEN nextval('pg_dist_shardid_seq') > 1599999 OR nextval('pg_dist_shardid_seq') < 1500000 - THEN 1500000 + CASE WHEN nextval('pg_dist_shardid_seq') > 1599999 OR nextval('pg_dist_shardid_seq') < 1500072 + THEN 1500072 ELSE nextval('pg_dist_shardid_seq')-2 END); CREATE TABLE isolation_table (id int PRIMARY KEY, value int); SELECT create_distributed_table('isolation_table', 'id'); + + -- different colocation id + CREATE TABLE isolation_table2 (id smallint PRIMARY KEY, value int); + SELECT create_distributed_table('isolation_table2', 'id'); } teardown { DROP TABLE isolation_table; + DROP TABLE isolation_table2; } session "s1" @@ -32,11 +37,13 @@ step "s1-begin" step "s1-load-cache" { TRUNCATE isolation_table; + TRUNCATE isolation_table2; } step "s1-insert" { INSERT INTO isolation_table VALUES (5, 10); + INSERT INTO isolation_table2 VALUES (5, 10); } step "s1-update" @@ -59,11 +66,26 @@ step "s1-copy" COPY isolation_table FROM PROGRAM 'echo "1,1\n2,2\n3,3\n4,4\n5,5"' WITH CSV; } -step "s1-isolate-tenant" +step "s1-isolate-tenant-same-coloc" { SELECT isolate_tenant_to_new_shard('isolation_table', 2, shard_transfer_mode => 'force_logical'); } +step "s1-isolate-tenant-same-coloc-blocking" +{ + SELECT isolate_tenant_to_new_shard('isolation_table', 2, shard_transfer_mode => 'block_writes'); +} + +step "s1-isolate-tenant-no-same-coloc" +{ + SELECT isolate_tenant_to_new_shard('isolation_table2', 2, shard_transfer_mode => 'force_logical'); +} + +step "s1-isolate-tenant-no-same-coloc-blocking" +{ + SELECT isolate_tenant_to_new_shard('isolation_table2', 2, shard_transfer_mode => 'block_writes'); +} + step "s1-commit" { COMMIT; @@ -122,7 +144,7 @@ step "s3-release-advisory-lock" // s1 can execute its DML command concurrently with s2 shard isolation => // s3 releases the advisory lock so that s2 can finish the transaction -// run tenant isolation while concurrently performing an DML and index creation +// run tenant isolation while concurrently performing an DML // we expect DML queries of s2 to succeed without being blocked. permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s1-begin" "s1-select" "s2-begin" "s2-isolate-tenant" "s1-update" "s1-commit" "s3-release-advisory-lock" "s2-commit" "s2-print-cluster" permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s1-begin" "s1-select" "s2-begin" "s2-isolate-tenant" "s1-delete" "s1-commit" "s3-release-advisory-lock" "s2-commit" "s2-print-cluster" @@ -135,8 +157,20 @@ permutation "s1-insert" "s3-acquire-advisory-lock" "s1-begin" "s1-select" "s2-be permutation "s3-acquire-advisory-lock" "s1-begin" "s1-select" "s2-begin" "s2-isolate-tenant" "s1-insert" "s1-commit" "s3-release-advisory-lock" "s2-commit" "s2-print-cluster" permutation "s3-acquire-advisory-lock" "s1-begin" "s1-select" "s2-begin" "s2-isolate-tenant" "s1-copy" "s1-commit" "s3-release-advisory-lock" "s2-commit" "s2-print-cluster" -// concurrent tenant isolation blocks on different shards of the same table (or any colocated table) -permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s1-begin" "s1-isolate-tenant" "s2-isolate-tenant" "s3-release-advisory-lock" "s1-commit" "s2-print-cluster" +// concurrent nonblocking tenant isolations with the same colocation id are not allowed +permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s2-isolate-tenant" "s1-isolate-tenant-same-coloc" "s3-release-advisory-lock" "s2-print-cluster" -// the same test above without loading the cache at first -permutation "s1-insert" "s3-acquire-advisory-lock" "s1-begin" "s1-isolate-tenant" "s2-isolate-tenant" "s3-release-advisory-lock" "s1-commit" "s2-print-cluster" +// concurrent blocking and nonblocking tenant isolations with the same colocation id are not allowed +permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s2-isolate-tenant" "s1-isolate-tenant-same-coloc-blocking" "s3-release-advisory-lock" "s2-print-cluster" + +// concurrent nonblocking tenant isolations in different transactions are not allowed +permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s2-isolate-tenant" "s1-isolate-tenant-no-same-coloc" "s3-release-advisory-lock" "s2-print-cluster" + +// concurrent nonblocking tenant isolations in the same transaction are not allowed +permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s2-begin" "s2-isolate-tenant" "s1-isolate-tenant-no-same-coloc" "s3-release-advisory-lock" "s2-commit" "s2-print-cluster" + +// concurrent blocking and nonblocking tenant isolations with different colocation ids in different transactions are allowed +permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s2-isolate-tenant" "s1-isolate-tenant-no-same-coloc-blocking" "s3-release-advisory-lock" "s2-print-cluster" + +// concurrent blocking and nonblocking tenant isolations with different colocation ids in the same transaction are allowed +permutation "s1-load-cache" "s1-insert" "s3-acquire-advisory-lock" "s2-isolate-tenant" "s1-isolate-tenant-no-same-coloc-blocking" "s3-release-advisory-lock" "s2-print-cluster" diff --git a/src/test/regress/split_schedule b/src/test/regress/split_schedule index eaa8eb799..62ba469bf 100644 --- a/src/test/regress/split_schedule +++ b/src/test/regress/split_schedule @@ -11,6 +11,7 @@ test: split_shard_replication_setup test: split_shard_replication_setup_remote_local test: split_shard_replication_setup_local test: split_shard_replication_colocated_setup +test: split_shard_release_dsm test: worker_split_copy_test test: worker_split_binary_copy_test test: worker_split_text_copy_test diff --git a/src/test/regress/sql/adaptive_executor.sql b/src/test/regress/sql/adaptive_executor.sql index a744437f1..f7d6c6f1e 100644 --- a/src/test/regress/sql/adaptive_executor.sql +++ b/src/test/regress/sql/adaptive_executor.sql @@ -7,8 +7,15 @@ SET citus.shard_count TO 4; SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 801009000; SELECT create_distributed_table('test','x'); +-- Add 1 row to each shard +SELECT get_shard_id_for_distribution_column('test', 1); INSERT INTO test VALUES (1,2); +SELECT get_shard_id_for_distribution_column('test', 3); INSERT INTO test VALUES (3,2); +SELECT get_shard_id_for_distribution_column('test', 6); +INSERT INTO test VALUES (8,2); +SELECT get_shard_id_for_distribution_column('test', 11); +INSERT INTO test VALUES (11,2); -- Set a very high slow start to avoid opening parallel connections SET citus.executor_slow_start_interval TO '60s'; @@ -26,7 +33,7 @@ END; SET citus.executor_slow_start_interval TO '10ms'; BEGIN; -SELECT count(*) FROM test a JOIN (SELECT x, pg_sleep(0.1) FROM test) b USING (x); +SELECT count(*) FROM test a JOIN (SELECT x, pg_sleep(0.2) FROM test) b USING (x); SELECT sum(result::bigint) FROM run_command_on_workers($$ SELECT count(*) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND query LIKE '%8010090%' diff --git a/src/test/regress/sql/alter_distributed_table.sql b/src/test/regress/sql/alter_distributed_table.sql index bfedf83b4..06dfc1a9d 100644 --- a/src/test/regress/sql/alter_distributed_table.sql +++ b/src/test/regress/sql/alter_distributed_table.sql @@ -84,6 +84,24 @@ SELECT * FROM partitioned_table ORDER BY 1, 2; SELECT * FROM partitioned_table_1_5 ORDER BY 1, 2; SELECT * FROM partitioned_table_6_10 ORDER BY 1, 2; +-- test altering partitioned table colocate_with:none +CREATE TABLE foo (x int, y int, t timestamptz default now()) PARTITION BY RANGE (t); +CREATE TABLE foo_1 PARTITION of foo for VALUES FROM ('2022-01-01') TO ('2022-12-31'); +CREATE TABLE foo_2 PARTITION of foo for VALUES FROM ('2023-01-01') TO ('2023-12-31'); + +SELECT create_distributed_table('foo','x'); + +CREATE TABLE foo_bar (x int, y int, t timestamptz default now()) PARTITION BY RANGE (t); +CREATE TABLE foo_bar_1 PARTITION of foo_bar for VALUES FROM ('2022-01-01') TO ('2022-12-31'); +CREATE TABLE foo_bar_2 PARTITION of foo_bar for VALUES FROM ('2023-01-01') TO ('2023-12-31'); + +SELECT create_distributed_table('foo_bar','x'); + +SELECT COUNT(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid::regclass::text in ('foo', 'foo_1', 'foo_2', 'foo_bar', 'foo_bar_1', 'foo_bar_2'); + +SELECT alter_distributed_table('foo', colocate_with => 'none'); + +SELECT COUNT(DISTINCT colocationid) FROM pg_dist_partition WHERE logicalrelid::regclass::text in ('foo', 'foo_1', 'foo_2', 'foo_bar', 'foo_bar_1', 'foo_bar_2'); -- test references CREATE TABLE referenced_dist_table (a INT UNIQUE); diff --git a/src/test/regress/sql/binary_protocol.sql b/src/test/regress/sql/binary_protocol.sql index 8c9761313..a6eefc14e 100644 --- a/src/test/regress/sql/binary_protocol.sql +++ b/src/test/regress/sql/binary_protocol.sql @@ -1,4 +1,5 @@ SET citus.shard_count = 2; +SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 4754000; CREATE SCHEMA binary_protocol; SET search_path TO binary_protocol, public; @@ -63,6 +64,8 @@ SELECT ARRAY[(col, col)::nested_composite_type] FROM composite_type_table; SELECT ARRAY[(col, col)::nested_composite_type_domain] FROM composite_type_table; +-- Confirm that aclitem doesn't have receive and send functions +SELECT typreceive, typsend FROM pg_type WHERE typname = 'aclitem'; CREATE TABLE binaryless_builtin ( col1 aclitem NOT NULL, col2 character varying(255) NOT NULL diff --git a/src/test/regress/sql/citus_local_tables_queries.sql b/src/test/regress/sql/citus_local_tables_queries.sql index a4f8b6e00..5f1d561b3 100644 --- a/src/test/regress/sql/citus_local_tables_queries.sql +++ b/src/test/regress/sql/citus_local_tables_queries.sql @@ -1,3 +1,13 @@ +-- +-- CITUS_LOCAL_TABLES_QUERIES +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + \set VERBOSITY terse SET citus.next_shard_id TO 1509000; diff --git a/src/test/regress/sql/citus_split_shard_columnar_partitioned.sql b/src/test/regress/sql/citus_split_shard_columnar_partitioned.sql index 5955be1a2..58d577f32 100644 --- a/src/test/regress/sql/citus_split_shard_columnar_partitioned.sql +++ b/src/test/regress/sql/citus_split_shard_columnar_partitioned.sql @@ -79,7 +79,7 @@ SET citus.shard_replication_factor TO 1; INNER JOIN pg_catalog.pg_class cls ON shard.logicalrelid = cls.oid INNER JOIN pg_catalog.pg_namespace ns ON cls.relnamespace = ns.oid WHERE node.noderole = 'primary' AND ns.nspname = 'citus_split_test_schema_columnar_partitioned' - ORDER BY logicalrelid, shardminvalue::BIGINT; + ORDER BY logicalrelid, shardminvalue::BIGINT, nodeport; -- END: Create table to split, along with other co-located tables. Add indexes, statistics etc. -- BEGIN: Create constraints for tables. @@ -176,7 +176,7 @@ SET citus.shard_replication_factor TO 1; INNER JOIN pg_catalog.pg_class cls ON shard.logicalrelid = cls.oid INNER JOIN pg_catalog.pg_namespace ns ON cls.relnamespace = ns.oid WHERE node.noderole = 'primary' AND ns.nspname = 'citus_split_test_schema_columnar_partitioned' - ORDER BY logicalrelid, shardminvalue::BIGINT; + ORDER BY logicalrelid, shardminvalue::BIGINT, nodeport; SELECT count(*) FROM reference_table; SELECT count(*) FROM colocated_partitioned_table; @@ -243,7 +243,7 @@ SET citus.shard_replication_factor TO 1; INNER JOIN pg_catalog.pg_class cls ON shard.logicalrelid = cls.oid INNER JOIN pg_catalog.pg_namespace ns ON cls.relnamespace = ns.oid WHERE node.noderole = 'primary' AND ns.nspname = 'citus_split_test_schema_columnar_partitioned' - ORDER BY logicalrelid, shardminvalue::BIGINT; + ORDER BY logicalrelid, shardminvalue::BIGINT, nodeport; SELECT count(*) FROM reference_table; SELECT count(*) FROM colocated_partitioned_table; diff --git a/src/test/regress/sql/columnar_chunk_filtering.sql b/src/test/regress/sql/columnar_chunk_filtering.sql index b8b2b411d..335401a20 100644 --- a/src/test/regress/sql/columnar_chunk_filtering.sql +++ b/src/test/regress/sql/columnar_chunk_filtering.sql @@ -130,11 +130,15 @@ INSERT INTO another_columnar_table SELECT generate_series(0,5); EXPLAIN (analyze on, costs off, timing off, summary off) SELECT a, y FROM multi_column_chunk_filtering, another_columnar_table WHERE x > 1; +SELECT plan_without_arrows($Q$ EXPLAIN (costs off, timing off, summary off) SELECT y, * FROM another_columnar_table; +$Q$); +SELECT plan_without_arrows($Q$ EXPLAIN (costs off, timing off, summary off) SELECT *, x FROM another_columnar_table; +$Q$); EXPLAIN (costs off, timing off, summary off) SELECT y, another_columnar_table FROM another_columnar_table; diff --git a/src/test/regress/sql/columnar_citus_integration.sql b/src/test/regress/sql/columnar_citus_integration.sql index a64a37108..566c3a9f6 100644 --- a/src/test/regress/sql/columnar_citus_integration.sql +++ b/src/test/regress/sql/columnar_citus_integration.sql @@ -428,11 +428,15 @@ SELECT create_distributed_table('weird_col_explain', 'bbbbbbbbbbbbbbbbbbbbbbbbb\ EXPLAIN (COSTS OFF, SUMMARY OFF) SELECT * FROM weird_col_explain; +\set VERBOSITY terse +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS OFF, SUMMARY OFF) SELECT *, "bbbbbbbbbbbbbbbbbbbbbbbbb\!bbbb'bbbbbbbbbbbbbbbbbbbbb''bbbbbbbb" FROM weird_col_explain WHERE "bbbbbbbbbbbbbbbbbbbbbbbbb\!bbbb'bbbbbbbbbbbbbbbbbbbbb''bbbbbbbb" * 2 > "aaaaaaaaaaaa$aaaaaa$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'aaaaaaaa'$a'!"; +$Q$); +\set VERBOSITY default -- should not project any columns EXPLAIN (COSTS OFF, SUMMARY OFF) diff --git a/src/test/regress/sql/columnar_memory.sql b/src/test/regress/sql/columnar_memory.sql index b5d251644..21bab57f5 100644 --- a/src/test/regress/sql/columnar_memory.sql +++ b/src/test/regress/sql/columnar_memory.sql @@ -73,7 +73,7 @@ INSERT INTO t SELECT i, 'last batch', 0 /* no need to record memusage per row */ FROM generate_series(1, 50000) i; -SELECT 1.0 * TopMemoryContext / :top_post BETWEEN 0.98 AND 1.02 AS top_growth_ok +SELECT CASE WHEN 1.0 * TopMemoryContext / :top_post BETWEEN 0.98 AND 1.03 THEN 1 ELSE 1.0 * TopMemoryContext / :top_post END AS top_growth FROM columnar_test_helpers.columnar_store_memory_stats(); -- before this change, max mem usage while executing inserts was 28MB and diff --git a/src/test/regress/sql/columnar_permissions.sql b/src/test/regress/sql/columnar_permissions.sql index 4f7c6cb27..11492bfed 100644 --- a/src/test/regress/sql/columnar_permissions.sql +++ b/src/test/regress/sql/columnar_permissions.sql @@ -31,16 +31,20 @@ select 1 from columnar.get_storage_id('no_access'::regclass); -- only tuples related to columnar_permissions should be visible select relation, chunk_group_row_limit, stripe_row_limit, compression, compression_level from columnar.options - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation; select relation, stripe_num, row_count, first_row_number from columnar.stripe - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; select relation, stripe_num, attr_num, chunk_group_num, value_count from columnar.chunk - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; select relation, stripe_num, chunk_group_num, row_count from columnar.chunk_group - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; truncate columnar_permissions; @@ -62,16 +66,20 @@ select alter_columnar_table_set('no_access', chunk_group_row_limit => 1111); -- should see tuples from both columnar_permissions and no_access select relation, chunk_group_row_limit, stripe_row_limit, compression, compression_level from columnar.options - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation; select relation, stripe_num, row_count, first_row_number from columnar.stripe - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; select relation, stripe_num, attr_num, chunk_group_num, value_count from columnar.chunk - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; select relation, stripe_num, chunk_group_num, row_count from columnar.chunk_group - where relation in ('no_access'::regclass, 'columnar_permissions'::regclass); + where relation in ('no_access'::regclass, 'columnar_permissions'::regclass) + order by relation, stripe_num; drop table columnar_permissions; drop table no_access; diff --git a/src/test/regress/sql/coordinator_shouldhaveshards.sql b/src/test/regress/sql/coordinator_shouldhaveshards.sql index c5e2b6177..3eb2de2e0 100644 --- a/src/test/regress/sql/coordinator_shouldhaveshards.sql +++ b/src/test/regress/sql/coordinator_shouldhaveshards.sql @@ -1,4 +1,14 @@ +-- +-- COORDINATOR_SHOULDHAVESHARDS +-- -- Test queries on a distributed table with shards on the coordinator +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; CREATE SCHEMA coordinator_shouldhaveshards; SET search_path TO coordinator_shouldhaveshards; diff --git a/src/test/regress/sql/cpu_priority.sql b/src/test/regress/sql/cpu_priority.sql index 787018622..921934f88 100644 --- a/src/test/regress/sql/cpu_priority.sql +++ b/src/test/regress/sql/cpu_priority.sql @@ -63,6 +63,9 @@ 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_distributed_table_concurrently.sql b/src/test/regress/sql/create_distributed_table_concurrently.sql new file mode 100644 index 000000000..be1984e6d --- /dev/null +++ b/src/test/regress/sql/create_distributed_table_concurrently.sql @@ -0,0 +1,141 @@ +create schema create_distributed_table_concurrently; +set search_path to create_distributed_table_concurrently; +set citus.shard_replication_factor to 1; + +-- make sure we have the coordinator in the metadata +SELECT 1 FROM citus_set_coordinator_host('localhost', :master_port); + +create table ref (id int primary key); +select create_reference_table('ref'); +insert into ref select s from generate_series(0,9) s; + +create table test (key text, id int references ref (id) on delete cascade, t timestamptz default now()) partition by range (t); +create table test_1 partition of test for values from ('2022-01-01') to ('2022-12-31'); +create table test_2 partition of test for values from ('2023-01-01') to ('2023-12-31'); +insert into test (key,id,t) select s,s%10, '2022-01-01'::date + interval '1 year' * (s%2) from generate_series(1,100) s; + +create table nocolo (x int, y int); + +-- test error conditions + +select create_distributed_table_concurrently('test','key', 'append'); +select create_distributed_table_concurrently('test','key', 'range'); +select create_distributed_table_concurrently('test','noexists', 'hash'); +select create_distributed_table_concurrently(0,'key'); +select create_distributed_table_concurrently('ref','id'); + +set citus.shard_replication_factor to 2; +select create_distributed_table_concurrently('test','key', 'hash'); +set citus.shard_replication_factor to 1; + +begin; +select create_distributed_table_concurrently('test','key'); +rollback; + +select create_distributed_table_concurrently('test','key'), create_distributed_table_concurrently('test','key'); + +select create_distributed_table_concurrently('nocolo','x'); +select create_distributed_table_concurrently('test','key', colocate_with := 'nocolo'); +select create_distributed_table_concurrently('test','key', colocate_with := 'noexists'); + +-- use colocate_with "default" +select create_distributed_table_concurrently('test','key', shard_count := 11); + +select shardcount from pg_dist_partition p join pg_dist_colocation c using (colocationid) where logicalrelid = 'test'::regclass; +select count(*) from pg_dist_shard where logicalrelid = 'test'::regclass; + +-- verify queries still work +select count(*) from test; +select key, id from test where key = '1'; +select count(*) from test_1; + +-- verify that the foreign key to reference table was created +begin; +delete from ref; +select count(*) from test; +rollback; + +-- verify that we can undistribute the table +begin; +select undistribute_table('test', cascade_via_foreign_keys := true); +rollback; + +-- verify that we can co-locate with create_distributed_table_concurrently +create table test2 (x text primary key, y text); +insert into test2 (x,y) select s,s from generate_series(1,100) s; +select create_distributed_table_concurrently('test2','x', colocate_with := 'test'); + +-- verify co-located joins work +select count(*) from test join test2 on (key = x); +select id, y from test join test2 on (key = x) where key = '1'; + +-- verify co-locaed foreign keys work +alter table test add constraint fk foreign key (key) references test2 (x); + +-------foreign key tests among different table types-------- +-- verify we do not allow foreign keys from reference table to distributed table concurrently +create table ref_table1(id int); +create table dist_table1(id int primary key); +select create_reference_table('ref_table1'); +alter table ref_table1 add constraint fkey foreign key (id) references dist_table1(id); +select create_distributed_table_concurrently('dist_table1', 'id'); + +-- verify we do not allow foreign keys from citus local table to distributed table concurrently +create table citus_local_table1(id int); +select citus_add_local_table_to_metadata('citus_local_table1'); +create table dist_table2(id int primary key); +alter table citus_local_table1 add constraint fkey foreign key (id) references dist_table2(id); +select create_distributed_table_concurrently('dist_table2', 'id'); + +-- verify we do not allow foreign keys from regular table to distributed table concurrently +create table local_table1(id int); +create table dist_table3(id int primary key); +alter table local_table1 add constraint fkey foreign key (id) references dist_table3(id); +select create_distributed_table_concurrently('dist_table3', 'id'); + +-- verify we allow foreign keys from distributed table to reference table concurrently +create table ref_table2(id int primary key); +select create_reference_table('ref_table2'); +create table dist_table4(id int references ref_table2(id)); +select create_distributed_table_concurrently('dist_table4', 'id'); + +insert into ref_table2 select s from generate_series(1,100) s; +insert into dist_table4 select s from generate_series(1,100) s; +select count(*) as total from dist_table4; + +-- verify we do not allow foreign keys from distributed table to citus local table concurrently +create table citus_local_table2(id int primary key); +select citus_add_local_table_to_metadata('citus_local_table2'); +create table dist_table5(id int references citus_local_table2(id)); +select create_distributed_table_concurrently('dist_table5', 'id'); + +-- verify we do not allow foreign keys from distributed table to regular table concurrently +create table local_table2(id int primary key); +create table dist_table6(id int references local_table2(id)); +select create_distributed_table_concurrently('dist_table6', 'id'); +-------foreign key tests among different table types-------- + +-- columnar tests -- + +-- create table with partitions +create table test_columnar (id int) partition by range (id); +create table test_columnar_1 partition of test_columnar for values from (1) to (51); +create table test_columnar_2 partition of test_columnar for values from (51) to (101) using columnar; + +-- load some data +insert into test_columnar (id) select s from generate_series(1,100) s; + +-- distribute table +select create_distributed_table_concurrently('test_columnar','id'); + +-- verify queries still work +select count(*) from test_columnar; +select id from test_columnar where id = 1; +select id from test_columnar where id = 51; +select count(*) from test_columnar_1; +select count(*) from test_columnar_2; + +-- columnar tests -- + +set client_min_messages to warning; +drop schema create_distributed_table_concurrently cascade; diff --git a/src/test/regress/sql/cte_inline.sql b/src/test/regress/sql/cte_inline.sql index 28691e35a..0e446d7f0 100644 --- a/src/test/regress/sql/cte_inline.sql +++ b/src/test/regress/sql/cte_inline.sql @@ -1,3 +1,13 @@ +-- +-- CTE_INLINE +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + CREATE SCHEMA cte_inline; SET search_path TO cte_inline; SET citus.next_shard_id TO 1960000; @@ -220,6 +230,10 @@ FROM USING (key); -- EXPLAIN should show the differences between MATERIALIZED and NOT MATERIALIZED + +\set VERBOSITY terse + +SELECT public.coordinator_plan_with_subplans($Q$ EXPLAIN (COSTS OFF) WITH cte_1 AS (SELECT * FROM test_table) SELECT count(*) @@ -228,6 +242,22 @@ FROM JOIN cte_1 as second_entry USING (key); +$Q$); + +\set VERBOSITY default + +-- enable_group_by_reordering is a new GUC introduced in PG15 +-- it does some optimization of the order of group by keys which results +-- in a different explain output plan between PG13/14 and PG15 +-- Hence we set that GUC to off. +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +SET enable_group_by_reordering TO off; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM SET enable_group_by_reordering TO off;$$); +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); EXPLAIN (COSTS OFF) WITH cte_1 AS NOT MATERIALIZED (SELECT * FROM test_table) SELECT @@ -238,6 +268,11 @@ FROM cte_1 as second_entry USING (key); +\if :server_version_ge_15 +RESET enable_group_by_reordering; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM RESET enable_group_by_reordering;$$); +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); -- ctes with volatile functions are not diff --git a/src/test/regress/sql/distributed_triggers.sql b/src/test/regress/sql/distributed_triggers.sql index e070a7e2e..2194d94d8 100644 --- a/src/test/regress/sql/distributed_triggers.sql +++ b/src/test/regress/sql/distributed_triggers.sql @@ -415,10 +415,15 @@ SELECT operation_type, product_sku, state_code FROM record_sale ORDER BY 1,2,3; -- --Test ALTER TRIGGER -- +-- Pre PG15, renaming the trigger on the parent table didn't rename the same trigger on +-- the children as well. Hence, let's not print the trigger names of the children +-- In PG15, rename is consistent for all partitions of the parent +-- This is tested in pg15.sql file. + CREATE VIEW sale_triggers AS SELECT tgname, tgrelid::regclass, tgenabled FROM pg_trigger - WHERE tgrelid::regclass::text like 'sale%' + WHERE tgrelid::regclass::text = 'sale' ORDER BY 1, 2; SELECT * FROM sale_triggers ORDER BY 1,2; diff --git a/src/test/regress/sql/drop_partitioned_table.sql b/src/test/regress/sql/drop_partitioned_table.sql index b1c64d5cb..c74dd93ac 100644 --- a/src/test/regress/sql/drop_partitioned_table.sql +++ b/src/test/regress/sql/drop_partitioned_table.sql @@ -204,6 +204,7 @@ SET search_path = drop_partitioned_table; SET citus.shard_count TO 1; SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 727000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 1344400; DROP EVENT TRIGGER new_trigger_for_drops; -- Case 1 - we should skip diff --git a/src/test/regress/sql/failure_connection_establishment.sql b/src/test/regress/sql/failure_connection_establishment.sql index 5029d40b7..5f364cacc 100644 --- a/src/test/regress/sql/failure_connection_establishment.sql +++ b/src/test/regress/sql/failure_connection_establishment.sql @@ -28,16 +28,6 @@ SELECT create_distributed_table('products', 'product_no'); -- Command below should error out since 'name' is not a distribution column ALTER TABLE products ADD CONSTRAINT p_key PRIMARY KEY(name); - --- we will insert a connection delay here as this query was the cause for an investigation --- into connection establishment problems -SET citus.node_connection_timeout TO 400; -SELECT citus.mitmproxy('conn.delay(500)'); - -ALTER TABLE products ADD CONSTRAINT p_key PRIMARY KEY(product_no); - -SELECT citus.mitmproxy('conn.allow()'); - CREATE TABLE r1 ( id int PRIMARY KEY, name text @@ -49,38 +39,40 @@ INSERT INTO r1 (id, name) VALUES SELECT create_reference_table('r1'); -SELECT citus.clear_network_traffic(); -SELECT citus.mitmproxy('conn.delay(500)'); - --- we cannot control which replica of the reference table will be queried and there is --- only one specific client we can control the connection for. --- by using round-robin task_assignment_policy we can force to hit both machines. --- and in the end, dumping the network traffic shows that the connection establishment --- is initiated to the node behind the proxy -SET client_min_messages TO ERROR; -SET citus.task_assignment_policy TO 'round-robin'; --- suppress the warning since we can't control which shard is chose first. Failure of this --- test would be if one of the queries does not return the result but an error. -SELECT name FROM r1 WHERE id = 2; -SELECT name FROM r1 WHERE id = 2; - --- verify a connection attempt was made to the intercepted node, this would have cause the --- connection to have been delayed and thus caused a timeout -SELECT * FROM citus.dump_network_traffic() WHERE conn=0; +-- Confirm that the first placement for both tables is on the second worker +-- node. This is necessary so we can use the first-replica task assignment +-- policy to first hit the node that we generate timeouts for. +SELECT placementid, p.shardid, logicalrelid, LEAST(2, groupid) groupid +FROM pg_dist_placement p JOIN pg_dist_shard s ON p.shardid = s.shardid +ORDER BY placementid; +SET citus.task_assignment_policy TO 'first-replica'; +-- we will insert a connection delay here as this query was the cause for an +-- investigation into connection establishment problems +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); +ALTER TABLE products ADD CONSTRAINT p_key PRIMARY KEY(product_no); +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); --- similar test with the above but this time on a --- distributed table instead of a reference table --- and with citus.force_max_query_parallelization is set +-- Make sure that we fall back to a working node for reads, even if it's not +-- the first choice in our task assignment policy. +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); +-- tests for connectivity checks +SELECT name FROM r1 WHERE id = 2; +RESET citus.node_connection_timeout; +SELECT citus.mitmproxy('conn.allow()'); + +-- similar test with the above but this time on a distributed table instead of +-- a reference table and with citus.force_max_query_parallelization is set SET citus.force_max_query_parallelization TO ON; -SELECT citus.mitmproxy('conn.delay(500)'); --- suppress the warning since we can't control which shard is chose first. Failure of this --- test would be if one of the queries does not return the result but an error. +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); SELECT count(*) FROM products; -SELECT count(*) FROM products; - +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); + SET citus.shard_replication_factor TO 1; CREATE TABLE single_replicatated(key int); SELECT create_distributed_table('single_replicatated', 'key'); @@ -88,15 +80,17 @@ SELECT create_distributed_table('single_replicatated', 'key'); -- this time the table is single replicated and we're still using the -- the max parallelization flag, so the query should fail SET citus.force_max_query_parallelization TO ON; -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); SELECT count(*) FROM single_replicatated; +RESET citus.force_max_query_parallelization; +RESET citus.node_connection_timeout; +SELECT citus.mitmproxy('conn.allow()'); -SET citus.force_max_query_parallelization TO OFF; -- one similar test, and this time on modification queries -- to see that connection establishement failures could -- fail the transaction (but not mark any placements as INVALID) -SELECT citus.mitmproxy('conn.allow()'); BEGIN; SELECT count(*) as invalid_placement_count @@ -105,9 +99,12 @@ FROM WHERE shardstate = 3 AND shardid IN (SELECT shardid from pg_dist_shard where logicalrelid = 'single_replicatated'::regclass); -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); INSERT INTO single_replicatated VALUES (100); COMMIT; +RESET citus.node_connection_timeout; +SELECT citus.mitmproxy('conn.allow()'); SELECT count(*) as invalid_placement_count FROM @@ -116,8 +113,6 @@ WHERE shardstate = 3 AND shardid IN (SELECT shardid from pg_dist_shard where logicalrelid = 'single_replicatated'::regclass); --- show that INSERT failed -SELECT citus.mitmproxy('conn.allow()'); SELECT count(*) FROM single_replicatated WHERE key = 100; @@ -154,8 +149,11 @@ SELECT citus.mitmproxy('conn.onCommandComplete(command="SELECT 1").cancel(' || p SELECT * FROM citus_check_connection_to_node('localhost', :worker_2_proxy_port); -- verify that the checks are not successful when timeouts happen on a connection -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 900; +SELECT citus.mitmproxy('conn.connect_delay(1400)'); SELECT * FROM citus_check_connection_to_node('localhost', :worker_2_proxy_port); +RESET citus.node_connection_timeout; +SELECT citus.mitmproxy('conn.allow()'); -- tests for citus_check_cluster_node_health @@ -183,10 +181,6 @@ SELECT * FROM citus_check_cluster_node_health(); SELECT citus.mitmproxy('conn.onAuthenticationOk().kill()'); SELECT * FROM citus_check_cluster_node_health(); --- cancel all connections to this node -SELECT citus.mitmproxy('conn.onAuthenticationOk().cancel(' || pg_backend_pid() || ')'); -SELECT * FROM citus_check_cluster_node_health(); - -- kill connection checks to this node SELECT citus.mitmproxy('conn.onQuery(query="^SELECT 1$").kill()'); SELECT * FROM citus_check_cluster_node_health(); @@ -197,7 +191,7 @@ SELECT * FROM citus_check_cluster_node_health(); RESET client_min_messages; +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); -SET citus.node_connection_timeout TO DEFAULT; DROP SCHEMA fail_connect CASCADE; SET search_path TO default; diff --git a/src/test/regress/sql/failure_create_distributed_table_concurrently.sql b/src/test/regress/sql/failure_create_distributed_table_concurrently.sql new file mode 100644 index 000000000..502c3940a --- /dev/null +++ b/src/test/regress/sql/failure_create_distributed_table_concurrently.sql @@ -0,0 +1,110 @@ +-- +-- failure_create_distributed_table_concurrently adds failure tests for creating distributed table concurrently without data. +-- + +-- due to different libpq versions +-- some warning messages differ +-- between local and CI +SET client_min_messages TO ERROR; + +-- setup db +CREATE SCHEMA IF NOT EXISTS create_dist_tbl_con; +SET SEARCH_PATH = create_dist_tbl_con; +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 + +-- make sure coordinator is in the metadata +SELECT citus_set_coordinator_host('localhost', 57636); + +-- create table that will be distributed concurrently +CREATE TABLE table_1 (id int PRIMARY KEY); + +-- START OF TESTS +SELECT citus.mitmproxy('conn.allow()'); + +-- failure on shard table creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE create_dist_tbl_con.table_1").kill()'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- cancellation on shard table creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE create_dist_tbl_con.table_1").cancel(' || :pid || ')'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on table constraints on replica identity creation +SELECT citus.mitmproxy('conn.onQuery(query="ALTER TABLE create_dist_tbl_con.table_1 ADD CONSTRAINT").kill()'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- cancellation on table constraints on replica identity creation +SELECT citus.mitmproxy('conn.onQuery(query="ALTER TABLE create_dist_tbl_con.table_1 ADD CONSTRAINT").cancel(' || :pid || ')'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on subscription creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE SUBSCRIPTION").kill()'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- cancellation on subscription creation +SELECT citus.mitmproxy('conn.onQuery(query="CREATE SUBSCRIPTION").cancel(' || :pid || ')'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on catching up LSN +SELECT citus.mitmproxy('conn.onQuery(query="SELECT min\(latest_end_lsn\) FROM pg_stat_subscription").kill()'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- cancellation on catching up LSN +SELECT citus.mitmproxy('conn.onQuery(query="SELECT min\(latest_end_lsn\) FROM pg_stat_subscription").cancel(' || :pid || ')'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- Comment out below flaky tests. It is caused by shard split cleanup which does not work properly yet. +-- -- failure on dropping subscription +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP SUBSCRIPTION").kill()'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- -- cancellation on dropping subscription +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP SUBSCRIPTION").cancel(' || :pid || ')'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- -- failure on dropping old shard +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS create_dist_tbl_con.table_1").kill()'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- -- cancellation on dropping old shard +-- SELECT citus.mitmproxy('conn.onQuery(query="DROP TABLE IF EXISTS create_dist_tbl_con.table_1").cancel(' || :pid || ')'); +-- SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on transaction begin +SELECT citus.mitmproxy('conn.onQuery(query="BEGIN").kill()'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on transaction begin +SELECT citus.mitmproxy('conn.onQuery(query="BEGIN").cancel(' || :pid || ')'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on transaction commit +SELECT citus.mitmproxy('conn.onQuery(query="COMMIT").kill()'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on transaction commit +SELECT citus.mitmproxy('conn.onQuery(query="COMMIT").cancel(' || :pid || ')'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on prepare transaction +SELECT citus.mitmproxy('conn.onQuery(query="PREPARE TRANSACTION").kill()'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- failure on prepare transaction +SELECT citus.mitmproxy('conn.onQuery(query="PREPARE TRANSACTION").cancel(' || :pid || ')'); +SELECT create_distributed_table_concurrently('table_1', 'id'); + +-- END OF TESTS + +SELECT citus.mitmproxy('conn.allow()'); + +-- Verify that the table can be distributed concurrently after unsuccessful attempts +SELECT create_distributed_table_concurrently('table_1', 'id'); +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); diff --git a/src/test/regress/sql/failure_create_distributed_table_non_empty.sql b/src/test/regress/sql/failure_create_distributed_table_non_empty.sql index 946903cd0..d6497f76f 100644 --- a/src/test/regress/sql/failure_create_distributed_table_non_empty.sql +++ b/src/test/regress/sql/failure_create_distributed_table_non_empty.sql @@ -224,7 +224,7 @@ SELECT count(*) FROM pg_dist_shard WHERE logicalrelid='create_distributed_table_ SELECT run_command_on_workers($$SELECT count(*) FROM information_schema.tables WHERE table_schema = 'create_distributed_table_non_empty_failure' and table_name LIKE 'test_table%'$$); -- in the first test, cancel the first connection we sent from the coordinator -SELECT citus.mitmproxy('conn.cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="CREATE TABLE").cancel(' || pg_backend_pid() || ')'); SELECT create_distributed_table('test_table', 'id'); SELECT citus.mitmproxy('conn.allow()'); SELECT count(*) FROM pg_dist_shard WHERE logicalrelid='create_distributed_table_non_empty_failure.test_table'::regclass; diff --git a/src/test/regress/sql/failure_ddl.sql b/src/test/regress/sql/failure_ddl.sql index a13d563e0..f63605fa9 100644 --- a/src/test/regress/sql/failure_ddl.sql +++ b/src/test/regress/sql/failure_ddl.sql @@ -89,13 +89,11 @@ ALTER TABLE test_table DROP COLUMN new_column; -- but now kill just after the worker sends response to -- COMMIT command, so we'll have lots of warnings but the command -- should have been committed both on the distributed table and the placements -SET client_min_messages TO WARNING; +SET client_min_messages TO ERROR; SELECT citus.mitmproxy('conn.onCommandComplete(command="^COMMIT").kill()'); ALTER TABLE test_table ADD COLUMN new_column INT; SELECT citus.mitmproxy('conn.allow()'); -SET client_min_messages TO ERROR; - SELECT array_agg(name::text ORDER BY name::text) FROM public.table_attrs where relid = 'test_table'::regclass; SELECT run_command_on_placements('test_table', $$SELECT array_agg(name::text ORDER BY name::text) FROM public.table_attrs where relid = '%s'::regclass;$$) ORDER BY 1; diff --git a/src/test/regress/sql/failure_failover_to_local_execution.sql b/src/test/regress/sql/failure_failover_to_local_execution.sql index 19722f6ba..12ae3afd9 100644 --- a/src/test/regress/sql/failure_failover_to_local_execution.sql +++ b/src/test/regress/sql/failure_failover_to_local_execution.sql @@ -23,11 +23,12 @@ INSERT INTO failover_to_local SELECT i, i::text FROM generate_series(0,20)i; -- even if the connection establishment fails, Citus can -- failover to local exection SET citus.node_connection_timeout TO 400; -SELECT citus.mitmproxy('conn.delay(500)'); +SELECT citus.mitmproxy('conn.connect_delay(500)'); SET citus.log_local_commands TO ON; SET client_min_messages TO DEBUG1; SELECT count(*) FROM failover_to_local; RESET client_min_messages; +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); -- if the remote query execution fails, Citus @@ -37,9 +38,11 @@ SELECT key / 0 FROM failover_to_local; -- if the local execution is disabled, Citus does -- not try to fallback to local execution SET citus.enable_local_execution TO false; -SELECT citus.mitmproxy('conn.delay(500)'); +SET citus.node_connection_timeout TO 400; +SELECT citus.mitmproxy('conn.connect_delay(500)'); SET citus.log_local_commands TO ON; SELECT count(*) FROM failover_to_local; +RESET citus.node_connection_timeout; SELECT citus.mitmproxy('conn.allow()'); RESET citus.enable_local_execution; diff --git a/src/test/regress/sql/failure_insert_select_repartition.sql b/src/test/regress/sql/failure_insert_select_repartition.sql index 0c65c2529..d61fbe9df 100644 --- a/src/test/regress/sql/failure_insert_select_repartition.sql +++ b/src/test/regress/sql/failure_insert_select_repartition.sql @@ -47,19 +47,25 @@ INSERT INTO target_table SELECT * FROM replicated_source_table; SELECT * FROM target_table ORDER BY a; -- --- kill fetch_intermediate_results +-- kill the COPY command that's created by fetch_intermediate_results -- this fails the fetch into target, so source replication doesn't matter -- and both should fail +-- We don't kill the fetch_intermediate_results query directly, because that +-- resulted in randomly failing tests on CI. The reason for that is that there +-- is a race condition, where killing the fetch_intermediate_results query +-- removes the data files before the fetch_intermediate_results query from the +-- other node can read them. In theory a similar race condition still exists +-- when killing the COPY, but CI doesn't hit that race condition in practice. -- TRUNCATE target_table; -SELECT citus.mitmproxy('conn.onQuery(query="fetch_intermediate_results").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="COPY").kill()'); INSERT INTO target_table SELECT * FROM source_table; SELECT * FROM target_table ORDER BY a; -SELECT citus.mitmproxy('conn.onQuery(query="fetch_intermediate_results").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="COPY").kill()'); INSERT INTO target_table SELECT * FROM replicated_source_table; SELECT * FROM target_table ORDER BY a; diff --git a/src/test/regress/sql/failure_multi_dml.sql b/src/test/regress/sql/failure_multi_dml.sql index 146c2a8d1..390c01461 100644 --- a/src/test/regress/sql/failure_multi_dml.sql +++ b/src/test/regress/sql/failure_multi_dml.sql @@ -212,6 +212,7 @@ COPY dml_test FROM STDIN WITH CSV; -- fail at PREPARED COMMIT as we use 2PC SELECT citus.mitmproxy('conn.onQuery(query="^COMMIT").kill()'); +SET client_min_messages TO ERROR; BEGIN; DELETE FROM dml_test WHERE id = 1; @@ -221,6 +222,8 @@ UPDATE dml_test SET name = 'alpha' WHERE id = 1; UPDATE dml_test SET name = 'gamma' WHERE id = 3; COMMIT; +RESET client_min_messages; + -- all changes should be committed because we injected -- the failure on the COMMIT time. And, we should not -- mark any placements as INVALID diff --git a/src/test/regress/sql/failure_online_move_shard_placement.sql b/src/test/regress/sql/failure_online_move_shard_placement.sql index eb8d68e20..282a2895c 100644 --- a/src/test/regress/sql/failure_online_move_shard_placement.sql +++ b/src/test/regress/sql/failure_online_move_shard_placement.sql @@ -60,8 +60,10 @@ SELECT citus.mitmproxy('conn.onQuery(query="^ALTER SUBSCRIPTION .* ENABLE").kill SELECT master_move_shard_placement(101, 'localhost', :worker_1_port, 'localhost', :worker_2_proxy_port); -- failure when enabling the subscriptions -SELECT citus.mitmproxy('conn.onQuery(query="^ALTER SUBSCRIPTION .* ENABLE").cancel(' || :pid || ')'); -SELECT master_move_shard_placement(101, 'localhost', :worker_1_port, 'localhost', :worker_2_proxy_port); +-- This test can be enabled again once this postgres bug is fixed: +-- https://www.postgresql.org/message-id/flat/HE1PR8303MB0075BF78AF1BE904050DA16BF7729%40HE1PR8303MB0075.EURPRD83.prod.outlook.com +-- SELECT citus.mitmproxy('conn.onQuery(query="^ALTER SUBSCRIPTION .* ENABLE").cancel(' || :pid || ')'); +-- SELECT master_move_shard_placement(101, 'localhost', :worker_1_port, 'localhost', :worker_2_proxy_port); -- failure on polling subscription state SELECT citus.mitmproxy('conn.onQuery(query="^SELECT count\(\*\) FROM pg_subscription_rel").kill()'); diff --git a/src/test/regress/sql/failure_parallel_connection.sql b/src/test/regress/sql/failure_parallel_connection.sql index 595b10ced..7ed6c1cae 100644 --- a/src/test/regress/sql/failure_parallel_connection.sql +++ b/src/test/regress/sql/failure_parallel_connection.sql @@ -35,7 +35,7 @@ SET citus.force_max_query_parallelization TO ON; BEGIN; SELECT count(*) FROM distributed_table JOIN reference_table USING (key); - SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).kill()'); + SELECT citus.mitmproxy('conn.onQuery(query="^SELECT count").after(1).kill()'); -- this query should not fail because each placement should be acceessed -- over a seperate connection diff --git a/src/test/regress/sql/failure_savepoints.sql b/src/test/regress/sql/failure_savepoints.sql index b586bcb5c..9291989a4 100644 --- a/src/test/regress/sql/failure_savepoints.sql +++ b/src/test/regress/sql/failure_savepoints.sql @@ -7,6 +7,7 @@ SELECT citus.mitmproxy('conn.allow()'); SET citus.shard_count = 2; SET citus.shard_replication_factor = 1; -- one shard per worker SET citus.next_shard_id TO 100950; +SET client_min_messages TO ERROR; ALTER SEQUENCE pg_catalog.pg_dist_placement_placementid_seq RESTART 150; CREATE TABLE artists ( @@ -200,6 +201,7 @@ SELECT * FROM ref; END; -- clean up +RESET client_min_messages; SELECT citus.mitmproxy('conn.allow()'); DROP TABLE artists; DROP TABLE researchers; diff --git a/src/test/regress/sql/failure_single_select.sql b/src/test/regress/sql/failure_single_select.sql index 9a4a82d12..8dfb33d3e 100644 --- a/src/test/regress/sql/failure_single_select.sql +++ b/src/test/regress/sql/failure_single_select.sql @@ -13,13 +13,13 @@ SELECT create_distributed_table('select_test', 'key'); -- put data in shard for which mitm node is first placement INSERT INTO select_test VALUES (3, 'test data'); -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").kill()'); SELECT * FROM select_test WHERE key = 3; SELECT * FROM select_test WHERE key = 3; -- kill after first SELECT; txn should fail as INSERT triggers -- 2PC (and placementis not marked bad) -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").kill()'); BEGIN; INSERT INTO select_test VALUES (3, 'more data'); @@ -35,12 +35,12 @@ TRUNCATE select_test; -- put data in shard for which mitm node is first placement INSERT INTO select_test VALUES (3, 'test data'); -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").cancel(' || pg_backend_pid() || ')'); SELECT * FROM select_test WHERE key = 3; SELECT * FROM select_test WHERE key = 3; -- cancel after first SELECT; txn should fail and nothing should be marked as invalid -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").cancel(' || pg_backend_pid() || ')'); BEGIN; INSERT INTO select_test VALUES (3, 'more data'); @@ -58,7 +58,7 @@ TRUNCATE select_test; -- cancel the second query -- error after second SELECT; txn should fail -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).cancel(' || pg_backend_pid() || ')'); BEGIN; INSERT INTO select_test VALUES (3, 'more data'); @@ -68,7 +68,7 @@ SELECT * FROM select_test WHERE key = 3; COMMIT; -- error after second SELECT; txn should fails the transaction -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).reset()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).reset()'); BEGIN; INSERT INTO select_test VALUES (3, 'more data'); @@ -77,7 +77,7 @@ INSERT INTO select_test VALUES (3, 'even more data'); SELECT * FROM select_test WHERE key = 3; COMMIT; -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(2).kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*pg_prepared_xacts").after(2).kill()'); SELECT recover_prepared_transactions(); SELECT recover_prepared_transactions(); @@ -93,12 +93,12 @@ SELECT create_distributed_table('select_test', 'key'); SET citus.max_cached_conns_per_worker TO 1; -- allow connection to be cached INSERT INTO select_test VALUES (1, 'test data'); -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).kill()'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).kill()'); SELECT * FROM select_test WHERE key = 1; SELECT * FROM select_test WHERE key = 1; -- now the same test with query cancellation -SELECT citus.mitmproxy('conn.onQuery(query="^SELECT").after(1).cancel(' || pg_backend_pid() || ')'); +SELECT citus.mitmproxy('conn.onQuery(query="^SELECT.*select_test").after(1).cancel(' || pg_backend_pid() || ')'); SELECT * FROM select_test WHERE key = 1; SELECT * FROM select_test WHERE key = 1; diff --git a/src/test/regress/sql/failure_test_helpers.sql b/src/test/regress/sql/failure_test_helpers.sql index 39c90205a..7053905ac 100644 --- a/src/test/regress/sql/failure_test_helpers.sql +++ b/src/test/regress/sql/failure_test_helpers.sql @@ -6,11 +6,6 @@ ALTER SYSTEM SET citus.recover_2pc_interval TO -1; ALTER SYSTEM set citus.enable_statistics_collection TO false; SELECT pg_reload_conf(); -CREATE OR REPLACE FUNCTION wait_until_metadata_sync(timeout INTEGER DEFAULT 15000) - RETURNS void - LANGUAGE C STRICT - AS 'citus'; - -- Add some helper functions for sending commands to mitmproxy CREATE FUNCTION citus.mitmproxy(text) RETURNS TABLE(result text) AS $$ diff --git a/src/test/regress/sql/failure_truncate.sql b/src/test/regress/sql/failure_truncate.sql index 059a62cd9..c5aad63fc 100644 --- a/src/test/regress/sql/failure_truncate.sql +++ b/src/test/regress/sql/failure_truncate.sql @@ -103,7 +103,7 @@ SELECT count(*) FROM test_table; TRUNCATE test_table; INSERT INTO test_table SELECT x,x FROM generate_series(1,20) as f(x); -SET client_min_messages TO WARNING; +SET client_min_messages TO ERROR; -- now kill just after the worker sends response to -- COMMIT command, so we'll have lots of warnings but the command -- should have been committed both on the distributed table and the placements @@ -112,7 +112,6 @@ TRUNCATE test_table; SELECT citus.mitmproxy('conn.allow()'); SELECT * FROM unhealthy_shard_count; SELECT count(*) FROM test_table; -SET client_min_messages TO ERROR; INSERT INTO test_table SELECT x,x FROM generate_series(1,20) as f(x); diff --git a/src/test/regress/sql/failure_vacuum.sql b/src/test/regress/sql/failure_vacuum.sql index b87b78195..2d79b03d5 100644 --- a/src/test/regress/sql/failure_vacuum.sql +++ b/src/test/regress/sql/failure_vacuum.sql @@ -20,8 +20,10 @@ VACUUM vacuum_test; SELECT citus.mitmproxy('conn.onQuery(query="^ANALYZE").kill()'); ANALYZE vacuum_test; +SET client_min_messages TO ERROR; SELECT citus.mitmproxy('conn.onQuery(query="^COMMIT").kill()'); ANALYZE vacuum_test; +RESET client_min_messages; SELECT citus.mitmproxy('conn.allow()'); SELECT recover_prepared_transactions(); diff --git a/src/test/regress/sql/function_propagation.sql b/src/test/regress/sql/function_propagation.sql index 579a1aa9f..eca10beb5 100644 --- a/src/test/regress/sql/function_propagation.sql +++ b/src/test/regress/sql/function_propagation.sql @@ -1,5 +1,6 @@ CREATE SCHEMA function_propagation_schema; SET search_path TO 'function_propagation_schema'; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 10000; -- Check whether supported dependencies can be distributed while propagating functions diff --git a/src/test/regress/sql/grant_on_schema_propagation.sql b/src/test/regress/sql/grant_on_schema_propagation.sql index 8769180df..1cb601ad6 100644 --- a/src/test/regress/sql/grant_on_schema_propagation.sql +++ b/src/test/regress/sql/grant_on_schema_propagation.sql @@ -1,6 +1,11 @@ -- -- GRANT_ON_SCHEMA_PROPAGATION -- +-- this test has different output for PG13/14 compared to PG15 +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; -- test grants are propagated when the schema is CREATE SCHEMA dist_schema; diff --git a/src/test/regress/sql/insert_select_repartition.sql b/src/test/regress/sql/insert_select_repartition.sql index 234d3374d..ee6065b88 100644 --- a/src/test/regress/sql/insert_select_repartition.sql +++ b/src/test/regress/sql/insert_select_repartition.sql @@ -1,3 +1,13 @@ +-- +-- INSERT_SELECT_REPARTITION +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + -- tests behaviour of INSERT INTO ... SELECT with repartitioning CREATE SCHEMA insert_select_repartition; SET search_path TO 'insert_select_repartition'; @@ -354,7 +364,9 @@ INSERT INTO target_table SELECT a, max(b) FROM source_table WHERE a BETWEEN 1 AND 2 GROUP BY a; +SELECT public.coordinator_plan($Q$ EXPLAIN EXECUTE insert_plan; +$Q$); SET client_min_messages TO DEBUG1; EXECUTE insert_plan; @@ -623,7 +635,9 @@ DO UPDATE SET create table table_with_sequences (x int, y int, z bigserial); insert into table_with_sequences values (1,1); select create_distributed_table('table_with_sequences','x'); +SELECT public.plan_without_result_lines($Q$ explain (costs off) insert into table_with_sequences select y, x from table_with_sequences; +$Q$); -- verify that we don't report repartitioned insert/select for tables -- with user-defined sequences. @@ -631,7 +645,9 @@ CREATE SEQUENCE user_defined_sequence; create table table_with_user_sequences (x int, y int, z bigint default nextval('user_defined_sequence')); insert into table_with_user_sequences values (1,1); select create_distributed_table('table_with_user_sequences','x'); +SELECT public.plan_without_result_lines($Q$ explain (costs off) insert into table_with_user_sequences select y, x from table_with_user_sequences; +$Q$); -- clean-up SET client_min_messages TO WARNING; diff --git a/src/test/regress/sql/intermediate_result_pruning.sql b/src/test/regress/sql/intermediate_result_pruning.sql index 58beb8df0..0ebe5825c 100644 --- a/src/test/regress/sql/intermediate_result_pruning.sql +++ b/src/test/regress/sql/intermediate_result_pruning.sql @@ -1,3 +1,13 @@ +-- +-- INTERMEDIATE_RESULT_PRUNING +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + CREATE SCHEMA intermediate_result_pruning; SET search_path TO intermediate_result_pruning; SET citus.log_intermediate_results TO TRUE; diff --git a/src/test/regress/sql/issue_5248.sql b/src/test/regress/sql/issue_5248.sql index fe42e3771..321e9df14 100644 --- a/src/test/regress/sql/issue_5248.sql +++ b/src/test/regress/sql/issue_5248.sql @@ -1,9 +1,22 @@ +-- +-- ISSUE_5248 +-- +-- This test file has an alternative output because of the change in the +-- backup modes of Postgres. Specifically, there is a renaming +-- issue: pg_stop_backup PRE PG15 vs pg_backup_stop PG15+ +-- The alternative output can be deleted when we drop support for PG14 +-- + CREATE SCHEMA issue_5248; SET search_path TO issue_5248; SET citus.shard_count TO 4; SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 3013000; +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset + create table countries( id serial primary key , name text @@ -190,7 +203,11 @@ FROM ( ( SELECT utc_offset FROM pg_catalog.pg_timezone_names limit 1 offset 4) limit 91) AS subq_3 +\if :server_version_ge_15 +WHERE pg_catalog.pg_backup_stop() > cast(NULL AS record) limit 100; +\else WHERE pg_catalog.pg_stop_backup() > cast(NULL AS pg_lsn) limit 100; +\endif SET client_min_messages TO WARNING; DROP SCHEMA issue_5248 CASCADE; diff --git a/src/test/regress/sql/json_table_select_only.sql b/src/test/regress/sql/json_table_select_only.sql new file mode 100644 index 000000000..250315a25 --- /dev/null +++ b/src/test/regress/sql/json_table_select_only.sql @@ -0,0 +1,330 @@ +-- +-- PG15+ test +-- +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 + +SET search_path TO "json table"; + +CREATE SCHEMA "json table"; +SET search_path TO "json table"; +CREATE TABLE jsonb_table_test (id bigserial, js jsonb); +SELECT create_distributed_table('jsonb_table_test', 'id'); + +-- insert some data +INSERT INTO jsonb_table_test (js) +VALUES ( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "d": [], "c": []}, + {"a": 2, "d": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "d": [1, 2], "c": []}, + {"x": "4", "d": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [100, 200, 300], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": [null]}, + {"x": "4", "b": [1, 2], "c": 2} + ]' +), +( + '[ + {"y": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "t": [1, 2], "c": []}, + {"x": "4", "b": [1, 200], "c": 96} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "100", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"t": 1, "b": [], "c": []}, + {"t": 2, "b": [1, 2, 3], "x": [10, null, 20]}, + {"t": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"U": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1000, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "T": [1, 2], "c": 123} + ]' +), +( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +), +( + '[ + {"ffa": 1, "b": [], "c": []}, + {"ffb": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"fffc": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); + +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + ) jt ORDER BY 1,2,3,4; + + + +-- default plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt ORDER BY 1,2,3,4; + +-- default plan (inner, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (inner) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt ORDER BY 1,2,3,4; + +-- default plan (inner, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + +-- default plan (outer, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt ORDER BY 1,2,3,4; + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt ORDER BY 1,2,3,4; + + +select + jt.*, b1 + 100 as b +from + json_table (jsonb + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt ORDER BY 1,2,3,4,5; + +-- Should succeed (JSON arguments are passed to root and nested paths) +SELECT * +FROM + generate_series(1, 4) x, + generate_series(1, 3) y, + JSON_TABLE(jsonb + '[[1,2,3],[2,3,4,5],[3,4,5,6]]', + 'strict $[*] ? (@[*] < $x)' + PASSING x AS x, y AS y + COLUMNS ( + y text FORMAT JSON PATH '$', + NESTED PATH 'strict $[*] ? (@ >= $y)' + COLUMNS ( + z int PATH '$' + ) + ) + ) jt ORDER BY 4,1,2,3; + +SET client_min_messages TO ERROR; +DROP SCHEMA "json table" CASCADE; + diff --git a/src/test/regress/sql/local_shard_execution.sql b/src/test/regress/sql/local_shard_execution.sql index b289f6bed..5e678e02a 100644 --- a/src/test/regress/sql/local_shard_execution.sql +++ b/src/test/regress/sql/local_shard_execution.sql @@ -1,3 +1,13 @@ +-- +-- LOCAL_SHARD_EXECUTION +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + CREATE SCHEMA local_shard_execution; SET search_path TO local_shard_execution; diff --git a/src/test/regress/sql/local_shard_execution_replicated.sql b/src/test/regress/sql/local_shard_execution_replicated.sql index a8fe72b98..3a15e52de 100644 --- a/src/test/regress/sql/local_shard_execution_replicated.sql +++ b/src/test/regress/sql/local_shard_execution_replicated.sql @@ -1,3 +1,13 @@ +-- +-- LOCAL_SHARD_EXECUTION_REPLICATED +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + CREATE SCHEMA local_shard_execution_replicated; SET search_path TO local_shard_execution_replicated; diff --git a/src/test/regress/sql/local_table_join.sql b/src/test/regress/sql/local_table_join.sql index 5eb9ece3f..d6f305d41 100644 --- a/src/test/regress/sql/local_table_join.sql +++ b/src/test/regress/sql/local_table_join.sql @@ -359,7 +359,7 @@ select typdefault from ( where typdefault > 'a' limit 1) as subq_0 where ( - select true from pg_catalog.pg_am limit 1 + select true as bool from pg_catalog.pg_am limit 1 ) ) as subq_1 ) as subq_2; @@ -376,7 +376,7 @@ select typdefault from ( where typdefault > 'a' limit 1) as subq_0 where ( - select true from pg_catalog.pg_am limit 1 + select true as bool from pg_catalog.pg_am limit 1 ) ) as subq_1 ) as subq_2; diff --git a/src/test/regress/sql/logical_replication.sql b/src/test/regress/sql/logical_replication.sql index c4f3eeb12..94b08a5d1 100644 --- a/src/test/regress/sql/logical_replication.sql +++ b/src/test/regress/sql/logical_replication.sql @@ -17,17 +17,23 @@ 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 SET search_path TO logical_replication; CREATE PUBLICATION citus_shard_move_publication_:postgres_oid FOR TABLE dist_6830000; \c - - - :master_port SET search_path TO logical_replication; -\set connection_string '\'user=postgres host=localhost port=' :worker_1_port '\'' +CREATE TABLE dist_6830000( + id bigserial PRIMARY KEY +); +\set connection_string '\'user=postgres host=localhost port=' :worker_1_port ' dbname=regression\'' CREATE SUBSCRIPTION citus_shard_move_subscription_:postgres_oid CONNECTION :connection_string PUBLICATION citus_shard_move_publication_:postgres_oid - WITH (slot_name=citus_shard_move_slot_:postgres_oid); + WITH (enabled=false, slot_name=citus_shard_move_slot_:postgres_oid); + SELECT count(*) from pg_subscription; SELECT count(*) from pg_publication; diff --git a/src/test/regress/sql/multi_alter_table_add_constraints.sql b/src/test/regress/sql/multi_alter_table_add_constraints.sql index 612e4d22b..2ff12ef66 100644 --- a/src/test/regress/sql/multi_alter_table_add_constraints.sql +++ b/src/test/regress/sql/multi_alter_table_add_constraints.sql @@ -239,6 +239,17 @@ SELECT create_distributed_table('products', 'product_no'); -- Command below should error out since 'name' is not a distribution column ALTER TABLE products ADD CONSTRAINT exc_name EXCLUDE USING btree (name with =); +-- check that we can disable the constraint check for EXCLUDE +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +ALTER TABLE products ADD CONSTRAINT exc_name EXCLUDE USING btree (name with =); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',12.0); +ROLLBACK; + -- We can add composite exclusion ALTER TABLE products ADD CONSTRAINT exc_pno_name EXCLUDE USING btree (product_no with =, name with =); @@ -399,6 +410,40 @@ INSERT INTO products VALUES(1,'product_1', 5); ALTER TABLE products ADD CONSTRAINT unn_pno UNIQUE(product_no); ROLLBACK; +-- check that we can disable the constraint check for CREATE UNIQUE INDEX +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +CREATE UNIQUE INDEX ON products (name, price); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',10.0); +ROLLBACK; + +-- check that we can disable the constraint check for CREATE UNIQUE INDEX CONCURRENTLY +SET citus.allow_unsafe_constraints TO on; +CREATE UNIQUE INDEX CONCURRENTLY product_idx ON products (name, price); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',10.0); +DROP INDEX product_idx; +TRUNCATE products; +RESET citus.allow_unsafe_constraints; + +-- check that we can disable the constraint check for ADD CONSTRAINT .. PRIMARY KEY +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +ALTER TABLE products ADD CONSTRAINT products_pk PRIMARY KEY (name, price); +-- not enforced across shards +INSERT INTO products VALUES (1,'boat',10.0); +INSERT INTO products VALUES (2,'boat',11.0); +-- enforced within the shard +INSERT INTO products VALUES (1,'boat',10.0); +ROLLBACK; + BEGIN; -- Add constraints ALTER TABLE products ADD CONSTRAINT unn_pno UNIQUE(product_no); diff --git a/src/test/regress/sql/multi_colocation_utils.sql b/src/test/regress/sql/multi_colocation_utils.sql index 245c2ce6d..57b7c366e 100644 --- a/src/test/regress/sql/multi_colocation_utils.sql +++ b/src/test/regress/sql/multi_colocation_utils.sql @@ -236,7 +236,7 @@ SELECT * FROM pg_dist_colocation WHERE colocationid = 4; -- check to see whether metadata is synced SELECT nodeport, unnest(result::jsonb[]) FROM run_command_on_workers($$ -SELECT array_agg(row_to_json(c)) FROM pg_dist_colocation c WHERE colocationid = 4 +SELECT coalesce(array_agg(row_to_json(c)), '{}') FROM pg_dist_colocation c WHERE colocationid = 4 $$); -- create dropped colocation group again @@ -591,3 +591,40 @@ DROP TABLE range_table; DROP TABLE none; DROP TABLE ref; DROP TABLE local_table; + + +CREATE TABLE tbl_1 (a INT, b INT); +CREATE TABLE tbl_2 (a INT, b INT); +CREATE TABLE tbl_3 (a INT, b INT); + +SELECT create_distributed_table('tbl_1', 'a', shard_count:=4); +SELECT create_distributed_table('tbl_2', 'a', shard_count:=4); +SELECT create_distributed_table('tbl_3', 'a', shard_count:=4, colocate_with:='NONE'); + +SELECT colocation_id AS col_id_1 FROM citus_tables WHERE table_name::text = 'tbl_1' \gset +SELECT colocation_id AS col_id_2 FROM citus_tables WHERE table_name::text = 'tbl_2' \gset +SELECT colocation_id AS col_id_3 FROM citus_tables WHERE table_name::text = 'tbl_3' \gset + +-- check that tables are colocated correctly +SELECT :col_id_1 = :col_id_2; +SELECT :col_id_1 = :col_id_3; + +-- check that there are separate rows for both colocation groups in pg_dist_colocation +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_1 +); +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_3 +); + +DROP TABLE tbl_1, tbl_3; + +-- check that empty colocation group is dropped and non-empty is not +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_1 +); +SELECT result FROM run_command_on_all_nodes(' + SELECT count(*) FROM pg_dist_colocation WHERE colocationid = ' || :col_id_3 +); + +DROP TABLE tbl_2; diff --git a/src/test/regress/sql/multi_copy.sql b/src/test/regress/sql/multi_copy.sql index fabadd88c..4ce089472 100644 --- a/src/test/regress/sql/multi_copy.sql +++ b/src/test/regress/sql/multi_copy.sql @@ -523,7 +523,6 @@ INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'packed_numbers_hash'::regclass::oid, 0); INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'super_packed_numbers_hash'::regclass::oid, 0); INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'table_to_distribute'::regclass::oid, 0); -INSERT INTO pg_catalog.pg_dist_object(classid, objid, objsubid) values('pg_class'::regclass::oid, 'second_dustbunnies'::regclass::oid, 0); SET client_min_messages TO ERROR; SELECT 1 FROM master_activate_node('localhost', :worker_1_port); diff --git a/src/test/regress/sql/multi_create_table_constraints.sql b/src/test/regress/sql/multi_create_table_constraints.sql index 281f8aaea..c0f8c7c50 100644 --- a/src/test/regress/sql/multi_create_table_constraints.sql +++ b/src/test/regress/sql/multi_create_table_constraints.sql @@ -31,6 +31,17 @@ CREATE TABLE pk_on_non_part_col ); SELECT create_distributed_table('pk_on_non_part_col', 'partition_col', 'hash'); +-- check that we can disable the constraint check +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +SELECT create_distributed_table('pk_on_non_part_col', 'partition_col', 'hash'); +-- not enforced across shards +INSERT INTO pk_on_non_part_col VALUES (1,1); +INSERT INTO pk_on_non_part_col VALUES (2,1); +-- enforced within shard +INSERT INTO pk_on_non_part_col VALUES (1,1); +END; + CREATE TABLE uq_on_non_part_col ( partition_col integer, @@ -46,6 +57,17 @@ CREATE TABLE ex_on_non_part_col ); SELECT create_distributed_table('ex_on_non_part_col', 'partition_col', 'hash'); +-- check that we can disable the constraint check +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +SELECT create_distributed_table('ex_on_non_part_col', 'partition_col', 'hash'); +-- not enforced across shards +INSERT INTO ex_on_non_part_col VALUES (1,1); +INSERT INTO ex_on_non_part_col VALUES (2,1); +-- enforced within shard +INSERT INTO ex_on_non_part_col VALUES (1,1); +END; + -- now show that Citus can distribute unique and EXCLUDE constraints that -- include the partition column for hash-partitioned tables. -- However, EXCLUDE constraints must include the partition column with @@ -76,6 +98,26 @@ SELECT create_distributed_table('uq_two_columns', 'partition_col', 'hash'); INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); +CREATE TABLE pk_on_two_non_part_cols +( + partition_col integer, + other_col integer, + other_col_2 text, + PRIMARY KEY (other_col, other_col_2) +); +SELECT create_distributed_table('pk_on_two_non_part_cols', 'partition_col', 'hash'); + +-- check that we can disable the constraint check +BEGIN; +SET LOCAL citus.allow_unsafe_constraints TO on; +SELECT create_distributed_table('pk_on_two_non_part_cols', 'partition_col', 'hash'); +-- not enforced across shards +INSERT INTO pk_on_two_non_part_cols VALUES (1,1,1); +INSERT INTO pk_on_two_non_part_cols VALUES (2,1,1); +-- enforced within shard +INSERT INTO pk_on_two_non_part_cols VALUES (1,1,1); +END; + CREATE TABLE ex_on_part_col ( partition_col integer, @@ -226,8 +268,8 @@ CREATE TABLE check_example SELECT create_distributed_table('check_example', 'partition_col', 'hash'); \c - - :public_worker_1_host :worker_1_port SELECT "Column", "Type", "Definition" FROM index_attrs WHERE - relid = 'check_example_partition_col_key_365056'::regclass; -SELECT "Constraint", "Definition" FROM table_checks WHERE relid='public.check_example_365056'::regclass; + relid = 'check_example_partition_col_key_365068'::regclass; +SELECT "Constraint", "Definition" FROM table_checks WHERE relid='public.check_example_365068'::regclass; \c - - :master_host :master_port -- Index-based constraints are created with shard-extended names, but others diff --git a/src/test/regress/sql/multi_deparse_shard_query.sql b/src/test/regress/sql/multi_deparse_shard_query.sql index f90f7eb4f..252f22fb9 100644 --- a/src/test/regress/sql/multi_deparse_shard_query.sql +++ b/src/test/regress/sql/multi_deparse_shard_query.sql @@ -1,7 +1,12 @@ -- -- MULTI_DEPARSE_SHARD_QUERY -- - +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; SET citus.next_shard_id TO 13100000; SET citus.shard_replication_factor TO 1; diff --git a/src/test/regress/sql/multi_explain.sql b/src/test/regress/sql/multi_explain.sql index 505eda13e..5086f7cf1 100644 --- a/src/test/regress/sql/multi_explain.sql +++ b/src/test/regress/sql/multi_explain.sql @@ -122,9 +122,11 @@ EXPLAIN (COSTS FALSE, FORMAT TEXT) GROUP BY l_quantity ORDER BY count_quantity, l_quantity; -- Test analyze (with TIMING FALSE and SUMMARY FALSE for consistent output) +SELECT public.plan_normalize_memory($Q$ EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE) SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity; +$Q$); -- EXPLAIN ANALYZE doesn't show worker tasks for repartition joins yet SET citus.shard_count TO 3; @@ -142,9 +144,11 @@ END; DROP TABLE t1, t2; -- Test query text output, with ANALYZE ON +SELECT public.plan_normalize_memory($Q$ EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE, VERBOSE TRUE) SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity; +$Q$); -- Test query text output, with ANALYZE OFF EXPLAIN (COSTS FALSE, ANALYZE FALSE, TIMING FALSE, SUMMARY FALSE, VERBOSE TRUE) @@ -250,6 +254,20 @@ FROM user_id) AS subquery; -- Union and left join subquery pushdown + +-- enable_group_by_reordering is a new GUC introduced in PG15 +-- it does some optimization of the order of group by keys which results +-- in a different explain output plan between PG13/14 and PG15 +-- Hence we set that GUC to off. +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +SET enable_group_by_reordering TO off; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM SET enable_group_by_reordering TO off;$$); +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); + EXPLAIN (COSTS OFF) SELECT avg(array_length(events, 1)) AS event_average, @@ -385,6 +403,12 @@ GROUP BY ORDER BY count_pay; +\if :server_version_ge_15 +RESET enable_group_by_reordering; +\endif +SELECT DISTINCT 1 FROM run_command_on_workers($$ALTER SYSTEM RESET enable_group_by_reordering;$$); +SELECT run_command_on_workers($$SELECT pg_reload_conf()$$); + -- Lateral join subquery pushdown -- set subquery_pushdown due to limit in the query SET citus.subquery_pushdown to ON; @@ -468,9 +492,11 @@ EXPLAIN (COSTS FALSE) DELETE FROM lineitem_hash_part; -- Test analyze (with TIMING FALSE and SUMMARY FALSE for consistent output) +SELECT public.plan_normalize_memory($Q$ EXPLAIN (COSTS FALSE, ANALYZE TRUE, TIMING FALSE, SUMMARY FALSE) SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity; +$Q$); SET citus.explain_all_tasks TO off; diff --git a/src/test/regress/sql/multi_extension.sql b/src/test/regress/sql/multi_extension.sql index 106b8bd4a..cfac9397c 100644 --- a/src/test/regress/sql/multi_extension.sql +++ b/src/test/regress/sql/multi_extension.sql @@ -294,6 +294,14 @@ SELECT * FROM multi_extension.print_extension_changes(); -- recreate public schema, and recreate citus_tables in the public schema by default CREATE SCHEMA public; +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15 +\gset +\if :server_version_ge_15 +ALTER SCHEMA public OWNER TO pg_database_owner; +\endif GRANT ALL ON SCHEMA public TO public; ALTER EXTENSION citus UPDATE TO '9.5-1'; ALTER EXTENSION citus UPDATE TO '10.0-4'; 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 3ee453074..b3996152f 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 @@ -9,6 +9,8 @@ SET citus.shard_replication_factor TO 1; CREATE SCHEMA fix_idx_names; SET search_path TO fix_idx_names, public; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 1370000; + -- stop metadata sync for one of the worker nodes so we test both cases SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); diff --git a/src/test/regress/sql/multi_insert_select.sql b/src/test/regress/sql/multi_insert_select.sql index baa176d9d..f35e16ccc 100644 --- a/src/test/regress/sql/multi_insert_select.sql +++ b/src/test/regress/sql/multi_insert_select.sql @@ -1,6 +1,12 @@ -- -- MULTI_INSERT_SELECT -- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; SET citus.next_shard_id TO 13300000; SET citus.next_placement_id TO 13300000; diff --git a/src/test/regress/sql/multi_insert_select_conflict.sql b/src/test/regress/sql/multi_insert_select_conflict.sql index ab05e9659..cb0ac01f5 100644 --- a/src/test/regress/sql/multi_insert_select_conflict.sql +++ b/src/test/regress/sql/multi_insert_select_conflict.sql @@ -1,3 +1,13 @@ +-- +-- MULTI_INSERT_SELECT_CONFLICT +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + CREATE SCHEMA on_conflict; SET search_path TO on_conflict, public; SET citus.next_shard_id TO 1900000; diff --git a/src/test/regress/sql/multi_metadata_sync.sql b/src/test/regress/sql/multi_metadata_sync.sql index 0cf828391..e0f7d8749 100644 --- a/src/test/regress/sql/multi_metadata_sync.sql +++ b/src/test/regress/sql/multi_metadata_sync.sql @@ -1,6 +1,11 @@ -- -- MULTI_METADATA_SYNC -- +-- this test has different output for PG13/14 compared to PG15 +-- In PG15, public schema is owned by pg_database_owner role +-- Relevant PG commit: b073c3ccd06e4cb845e121387a43faa8c68a7b62 +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; -- Tests for metadata snapshot functions, metadata syncing functions and propagation of -- metadata changes to MX tables. @@ -10,6 +15,7 @@ SELECT stop_metadata_sync_to_node('localhost', :worker_1_port); SELECT stop_metadata_sync_to_node('localhost', :worker_2_port); ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 1310000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 2; SET citus.replicate_reference_tables_on_activate TO off; SELECT nextval('pg_catalog.pg_dist_placement_placementid_seq') AS last_placement_id diff --git a/src/test/regress/sql/multi_mx_create_table.sql b/src/test/regress/sql/multi_mx_create_table.sql index 0a685e5ce..52270409e 100644 --- a/src/test/regress/sql/multi_mx_create_table.sql +++ b/src/test/regress/sql/multi_mx_create_table.sql @@ -3,6 +3,7 @@ -- ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 1220000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 1220000; SELECT start_metadata_sync_to_node('localhost', :worker_1_port); SELECT start_metadata_sync_to_node('localhost', :worker_2_port); diff --git a/src/test/regress/sql/multi_mx_insert_select_repartition.sql b/src/test/regress/sql/multi_mx_insert_select_repartition.sql index e086444cf..4a9c8c96f 100644 --- a/src/test/regress/sql/multi_mx_insert_select_repartition.sql +++ b/src/test/regress/sql/multi_mx_insert_select_repartition.sql @@ -1,4 +1,14 @@ +-- +-- MULTI_MX_INSERT_SELECT_REPARTITION +-- -- Test behaviour of repartitioned INSERT ... SELECT in MX setup +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; CREATE SCHEMA multi_mx_insert_select_repartition; SET search_path TO multi_mx_insert_select_repartition; diff --git a/src/test/regress/sql/multi_replicate_reference_table.sql b/src/test/regress/sql/multi_replicate_reference_table.sql index 50dd0a5b2..c34007ad7 100644 --- a/src/test/regress/sql/multi_replicate_reference_table.sql +++ b/src/test/regress/sql/multi_replicate_reference_table.sql @@ -505,9 +505,10 @@ ORDER BY 1,4,5; SELECT 1 FROM master_remove_node('localhost', :worker_2_port); -CREATE TABLE ref_table(a int); +CREATE TABLE ref_table(id bigserial PRIMARY KEY, a int); +CREATE INDEX ON ref_table (a); SELECT create_reference_table('ref_table'); -INSERT INTO ref_table SELECT * FROM generate_series(1, 10); +INSERT INTO ref_table(a) SELECT * FROM generate_series(1, 10); -- verify direct call to citus_copy_shard_placement errors if target node is not yet added SELECT citus_copy_shard_placement( @@ -568,7 +569,7 @@ ROLLBACK; BEGIN; SELECT count(*) FROM ref_table; SELECT replicate_reference_tables('block_writes'); -INSERT INTO ref_table VALUES (11); +INSERT INTO ref_table(a) VALUES (11); SELECT count(*), sum(a) FROM ref_table; UPDATE ref_table SET a = a + 1; SELECT sum(a) FROM ref_table; @@ -638,6 +639,12 @@ SELECT result::int - :ref_table_placements FROM run_command_on_workers('SELECT count(*) FROM pg_dist_placement a, pg_dist_shard b, pg_class c WHERE a.shardid=b.shardid AND b.logicalrelid=c.oid AND c.relname=''ref_table''') WHERE nodeport=:worker_1_port; +-- test that we can use non-blocking rebalance +SELECT 1 FROM master_remove_node('localhost', :worker_2_port); +SELECT 1 FROM master_add_node('localhost', :worker_2_port); + +SELECT rebalance_table_shards(shard_transfer_mode := 'force_logical'); + -- test that metadata is synced on replicate_reference_tables SELECT 1 FROM master_remove_node('localhost', :worker_2_port); SELECT 1 FROM master_add_node('localhost', :worker_2_port); @@ -660,7 +667,7 @@ SELECT create_distributed_table('dist_table', 'a'); INSERT INTO dist_table SELECT i, i * i FROM generate_series(1, 20) i; TRUNCATE ref_table; -INSERT INTO ref_table SELECT 2 * i FROM generate_series(1, 5) i; +INSERT INTO ref_table(a) SELECT 2 * i FROM generate_series(1, 5) i; \c - - - :worker_1_port diff --git a/src/test/regress/sql/multi_select_distinct.sql b/src/test/regress/sql/multi_select_distinct.sql index 8254da5ba..75dd99da0 100644 --- a/src/test/regress/sql/multi_select_distinct.sql +++ b/src/test/regress/sql/multi_select_distinct.sql @@ -113,11 +113,13 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. We expect to see sort+unique -- instead of aggregate plan node to handle distinct. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT count(*) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1; +$Q$); SET enable_hashagg TO on; @@ -140,12 +142,14 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. Similar to the explain of -- the query above. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT l_suppkey, count(*) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1 LIMIT 10; +$Q$); SET enable_hashagg TO on; @@ -169,12 +173,14 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. This explain errors out due -- to a bug right now, expectation must be corrected after fixing it. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT l_suppkey, avg(l_partkey) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1,2 LIMIT 10; +$Q$); SET enable_hashagg TO on; @@ -197,12 +203,14 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. We expect to see sort+unique to -- handle distinct on. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT ON (l_suppkey) avg(l_partkey) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY l_suppkey,1 LIMIT 10; +$Q$); SET enable_hashagg TO on; @@ -224,12 +232,14 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. This explain errors out due -- to a bug right now, expectation must be corrected after fixing it. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT avg(ceil(l_partkey / 2)) FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1 LIMIT 10; +$Q$); SET enable_hashagg TO on; @@ -251,12 +261,14 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled. This explain errors out due -- to a bug right now, expectation must be corrected after fixing it. SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT sum(l_suppkey) + count(l_partkey) AS dis FROM lineitem_hash_part GROUP BY l_suppkey, l_linenumber ORDER BY 1 LIMIT 10; +$Q$); SET enable_hashagg TO on; @@ -270,22 +282,26 @@ SELECT DISTINCT * -- explain the query to see actual plan. We expect to see only one aggregation -- node since group by columns guarantees the uniqueness. +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT * FROM lineitem_hash_part GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 ORDER BY 1,2 LIMIT 10; +$Q$); -- check the plan if the hash aggreate is disabled. We expect to see only one -- aggregation node since group by columns guarantees the uniqueness. SET enable_hashagg TO off; +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT * FROM lineitem_hash_part GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 ORDER BY 1,2 LIMIT 10; +$Q$); SET enable_hashagg TO on; @@ -329,30 +345,36 @@ EXPLAIN (COSTS FALSE) -- check the plan if the hash aggreate is disabled SET enable_hashagg TO off; +SELECT public.plan_without_result_lines($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT ceil(count(case when l_partkey > 100000 THEN 1 ELSE 0 END) / 2) AS count FROM lineitem_hash_part GROUP BY l_suppkey ORDER BY 1; +$Q$); SET enable_hashagg TO on; -- explain the query to see actual plan with array_agg aggregation. +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT array_agg(l_linenumber), array_length(array_agg(l_linenumber), 1) FROM lineitem_hash_part GROUP BY l_orderkey ORDER BY 2 LIMIT 15; +$Q$); -- check the plan if the hash aggreate is disabled. SET enable_hashagg TO off; +SELECT coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT DISTINCT array_agg(l_linenumber), array_length(array_agg(l_linenumber), 1) FROM lineitem_hash_part GROUP BY l_orderkey ORDER BY 2 LIMIT 15; +$Q$); SET enable_hashagg TO on; diff --git a/src/test/regress/sql/multi_test_helpers.sql b/src/test/regress/sql/multi_test_helpers.sql index 9e70cb971..b5d4b9cd9 100644 --- a/src/test/regress/sql/multi_test_helpers.sql +++ b/src/test/regress/sql/multi_test_helpers.sql @@ -20,10 +20,15 @@ END; $$LANGUAGE plpgsql; -- Create a function to ignore worker plans in explain output +-- Also remove extra "-> Result" lines for PG15 support CREATE OR REPLACE FUNCTION coordinator_plan(explain_command text, out query_plan text) RETURNS SETOF TEXT AS $$ BEGIN FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') + THEN + CONTINUE; + END IF; RETURN next; IF query_plan LIKE '%Task Count:%' THEN @@ -33,6 +38,69 @@ BEGIN RETURN; END; $$ language plpgsql; +-- Create a function to ignore worker plans in explain output +-- It also shows task count for plan and subplans +-- Also remove extra "-> Result" lines for PG15 support +CREATE OR REPLACE FUNCTION coordinator_plan_with_subplans(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +DECLARE + task_count_line_reached boolean := false; +BEGIN + FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') THEN + CONTINUE; + END IF; + IF NOT task_count_line_reached THEN + RETURN next; + END IF; + IF query_plan LIKE '%Task Count:%' THEN + IF NOT task_count_line_reached THEN + SELECT true INTO task_count_line_reached; + ELSE + RETURN next; + END IF; + END IF; + END LOOP; + RETURN; +END; $$ language plpgsql; + +-- Create a function to ignore "-> Result" lines for PG15 support +-- In PG15 there are some extra "-> Result" lines +CREATE OR REPLACE FUNCTION plan_without_result_lines(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +BEGIN + FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') THEN + CONTINUE; + END IF; + RETURN next; + END LOOP; + RETURN; +END; $$ language plpgsql; + +-- Create a function to normalize Memory Usage, Buckets, Batches +CREATE OR REPLACE FUNCTION plan_normalize_memory(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +BEGIN + FOR query_plan IN execute explain_command LOOP + query_plan := regexp_replace(query_plan, '(Memory( Usage)?|Buckets|Batches): \S*', '\1: xxx', 'g'); + RETURN NEXT; + END LOOP; +END; $$ language plpgsql; + +-- Create a function to remove arrows from the explain plan +CREATE OR REPLACE FUNCTION plan_without_arrows(explain_command text, out query_plan text) +RETURNS SETOF TEXT AS $$ +BEGIN + FOR query_plan IN execute explain_command LOOP + IF (query_plan LIKE '%-> Result%' OR query_plan = 'Result') THEN + CONTINUE; + END IF; + query_plan := regexp_replace(query_plan, '( )*-> (.*)', '\2', 'g'); + RETURN NEXT; + END LOOP; +END; $$ language plpgsql; + -- helper function that returns true if output of given explain has "is not null" (case in-sensitive) CREATE OR REPLACE FUNCTION explain_has_is_not_null(explain_command text) RETURNS BOOLEAN AS $$ diff --git a/src/test/regress/sql/multi_transaction_recovery.sql b/src/test/regress/sql/multi_transaction_recovery.sql index 5156c83e3..5b5afb2e2 100644 --- a/src/test/regress/sql/multi_transaction_recovery.sql +++ b/src/test/regress/sql/multi_transaction_recovery.sql @@ -193,7 +193,11 @@ SELECT create_distributed_table('test_2pcskip', 'a'); INSERT INTO test_2pcskip SELECT i FROM generate_series(0, 5)i; SELECT recover_prepared_transactions(); -SELECT shardid INTO selected_shard FROM pg_dist_shard WHERE logicalrelid='test_2pcskip'::regclass LIMIT 1; +SELECT shardid INTO selected_shard +FROM citus_shards +WHERE table_name='test_2pcskip'::regclass AND nodeport = :worker_1_port +LIMIT 1; + SELECT COUNT(*) FROM pg_dist_transaction; BEGIN; SET LOCAL citus.defer_drop_after_shard_move TO OFF; diff --git a/src/test/regress/sql/multi_utilities.sql b/src/test/regress/sql/multi_utilities.sql index 90d40d586..86d86603b 100644 --- a/src/test/regress/sql/multi_utilities.sql +++ b/src/test/regress/sql/multi_utilities.sql @@ -7,6 +7,9 @@ SET citus.next_shard_id TO 990000; SET citus.shard_count TO 2; SET citus.shard_replication_factor TO 1; +CREATE SCHEMA multi_utilities; +SET search_path TO multi_utilities, public; + CREATE TABLE sharded_table ( name text, id bigint ); SELECT create_distributed_table('sharded_table', 'id', 'hash'); @@ -129,6 +132,7 @@ SELECT master_create_worker_shards('second_dustbunnies', 1, 2); -- run VACUUM and ANALYZE against the table on the master \c - - :master_host :master_port +SET search_path TO multi_utilities, public; VACUUM dustbunnies; ANALYZE dustbunnies; @@ -138,6 +142,7 @@ VACUUM (FULL) dustbunnies; VACUUM ANALYZE dustbunnies; \c - - :public_worker_1_host :worker_1_port +SET search_path TO multi_utilities, public; -- disable auto-VACUUM for next test ALTER TABLE dustbunnies_990002 SET (autovacuum_enabled = false); SELECT relfrozenxid AS frozenxid FROM pg_class WHERE oid='dustbunnies_990002'::regclass @@ -145,11 +150,13 @@ SELECT relfrozenxid AS frozenxid FROM pg_class WHERE oid='dustbunnies_990002'::r -- send a VACUUM FREEZE after adding a new row \c - - :master_host :master_port +SET search_path TO multi_utilities, public; INSERT INTO dustbunnies VALUES (5, 'peter'); VACUUM (FREEZE) dustbunnies; -- verify that relfrozenxid increased \c - - :public_worker_1_host :worker_1_port +SET search_path TO multi_utilities, public; SELECT relfrozenxid::text::integer > :frozenxid AS frozen_performed FROM pg_class WHERE oid='dustbunnies_990002'::regclass; @@ -159,15 +166,18 @@ WHERE tablename = 'dustbunnies_990002' ORDER BY attname; -- add NULL values, then perform column-specific ANALYZE \c - - :master_host :master_port +SET search_path TO multi_utilities, public; INSERT INTO dustbunnies VALUES (6, NULL, NULL); ANALYZE dustbunnies (name); -- verify that name's NULL ratio is updated but age's is not \c - - :public_worker_1_host :worker_1_port +SET search_path TO multi_utilities, public; SELECT attname, null_frac FROM pg_stats WHERE tablename = 'dustbunnies_990002' ORDER BY attname; \c - - :master_host :master_port +SET search_path TO multi_utilities, public; SET citus.log_remote_commands TO ON; -- check for multiple table vacuum @@ -219,11 +229,13 @@ VACUUM; insert into local_vacuum_table select i from generate_series(1,1000000) i; delete from local_vacuum_table; VACUUM local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); +SELECT CASE WHEN s BETWEEN 20000000 AND 25000000 THEN 22500000 ELSE s END +FROM pg_total_relation_size('local_vacuum_table') s ; -- vacuum full deallocates pages of dead tuples whereas normal vacuum only marks dead tuples on visibility map VACUUM FULL local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); +SELECT CASE WHEN s BETWEEN 0 AND 50000 THEN 25000 ELSE s END size +FROM pg_total_relation_size('local_vacuum_table') s ; -- should propagate to all workers because table is reference table VACUUM reference_vacuum_table; @@ -245,12 +257,14 @@ VACUUM (DISABLE_PAGE_SKIPPING false) local_vacuum_table; insert into local_vacuum_table select i from generate_series(1,1000000) i; delete from local_vacuum_table; VACUUM (INDEX_CLEANUP OFF, PARALLEL 1) local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); +SELECT CASE WHEN s BETWEEN 50000000 AND 70000000 THEN 60000000 ELSE s END size +FROM pg_total_relation_size('local_vacuum_table') s ; insert into local_vacuum_table select i from generate_series(1,1000000) i; delete from local_vacuum_table; VACUUM (INDEX_CLEANUP ON, PARALLEL 1) local_vacuum_table; -SELECT pg_size_pretty( pg_total_relation_size('local_vacuum_table') ); +SELECT CASE WHEN s BETWEEN 20000000 AND 49999999 THEN 35000000 ELSE s END size +FROM pg_total_relation_size('local_vacuum_table') s ; -- vacuum (truncate false) should not attempt to truncate off any empty pages at the end of the table (default is true) insert into local_vacuum_table select i from generate_series(1,1000000) i; @@ -295,6 +309,7 @@ CREATE TABLE dist (a INT); SELECT create_distributed_table ('dist', 'a'); SET citus.log_remote_commands TO ON; +SET citus.grep_remote_commands = '%ANALYZE%'; -- should propagate to all workers because no table is specified ANALYZE; @@ -321,3 +336,8 @@ SET citus.enable_ddl_propagation TO ON; -- analyze only specified columns for corresponding tables ANALYZE loc(b), dist(a); + +RESET citus.log_remote_commands; +RESET citus.grep_remote_commands; +SET client_min_messages TO WARNING; +DROP SCHEMA multi_utilities CASCADE; diff --git a/src/test/regress/sql/multi_view.sql b/src/test/regress/sql/multi_view.sql index 181fb4e70..193d669f6 100644 --- a/src/test/regress/sql/multi_view.sql +++ b/src/test/regress/sql/multi_view.sql @@ -374,6 +374,7 @@ VACUUM ANALYZE users_table; -- explain tests EXPLAIN (COSTS FALSE) SELECT user_id FROM recent_selected_users GROUP BY 1 ORDER BY 1; +SELECT public.coordinator_plan($Q$ EXPLAIN (COSTS FALSE) SELECT * FROM ( (SELECT user_id FROM recent_users) @@ -381,6 +382,7 @@ EXPLAIN (COSTS FALSE) SELECT * (SELECT user_id FROM selected_users) ) u WHERE user_id < 4 AND user_id > 1 ORDER BY user_id; +$Q$); EXPLAIN (COSTS FALSE) SELECT et.* FROM recent_10_users JOIN events_table et USING(user_id) ORDER BY et.time DESC LIMIT 10; SET citus.subquery_pushdown to ON; diff --git a/src/test/regress/sql/mx_coordinator_shouldhaveshards.sql b/src/test/regress/sql/mx_coordinator_shouldhaveshards.sql index 2b2d9b118..9a892a457 100644 --- a/src/test/regress/sql/mx_coordinator_shouldhaveshards.sql +++ b/src/test/regress/sql/mx_coordinator_shouldhaveshards.sql @@ -1,3 +1,13 @@ +-- +-- MX_COORDINATOR_SHOULDHAVESHARDS +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + CREATE SCHEMA mx_coordinator_shouldhaveshards; SET search_path TO mx_coordinator_shouldhaveshards; diff --git a/src/test/regress/sql/pg12.sql b/src/test/regress/sql/pg12.sql index db0b3c3fc..d7620140d 100644 --- a/src/test/regress/sql/pg12.sql +++ b/src/test/regress/sql/pg12.sql @@ -348,8 +348,12 @@ ROLLBACK; BEGIN; -- drop some of the columns not having "generated always as stored" expressions - -- this would drop generated columns too - ALTER TABLE generated_stored_ref DROP COLUMN col_1; + -- PRE PG15, this would drop generated columns too + -- In PG15, CASCADE option must be specified + -- Relevant PG Commit: cb02fcb4c95bae08adaca1202c2081cfc81a28b5 + SET client_min_messages TO WARNING; + ALTER TABLE generated_stored_ref DROP COLUMN col_1 CASCADE; + RESET client_min_messages; ALTER TABLE generated_stored_ref DROP COLUMN col_4; -- show that undistribute_table works fine diff --git a/src/test/regress/sql/pg14.sql b/src/test/regress/sql/pg14.sql index e40d52c10..7c5faa94f 100644 --- a/src/test/regress/sql/pg14.sql +++ b/src/test/regress/sql/pg14.sql @@ -1,7 +1,7 @@ SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int > 13 AS server_version_above_thirteen +SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 \gset -\if :server_version_above_thirteen +\if :server_version_ge_14 \else \q \endif @@ -92,9 +92,9 @@ SET citus.shard_replication_factor TO 1; CREATE TABLE col_compression (a TEXT COMPRESSION pglz, b TEXT); SELECT create_distributed_table('col_compression', 'a', shard_count:=4); -SELECT attname || ' ' || attcompression AS column_compression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'col\_compression%' AND attnum > 0 ORDER BY 1; +SELECT attname || ' ' || attcompression::text AS column_compression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'col\_compression%' AND attnum > 0 ORDER BY 1; SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); -- test column compression propagation in rebalance @@ -103,20 +103,20 @@ SELECT citus_move_shard_placement((SELECT * FROM moving_shard), :'public_worker_ SELECT rebalance_table_shards('col_compression', rebalance_strategy := 'by_shard_count', shard_transfer_mode := 'block_writes'); CALL citus_cleanup_orphaned_shards(); SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); -- test propagation of ALTER TABLE .. ALTER COLUMN .. SET COMPRESSION .. ALTER TABLE col_compression ALTER COLUMN b SET COMPRESSION pglz; ALTER TABLE col_compression ALTER COLUMN a SET COMPRESSION default; SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); -- test propagation of ALTER TABLE .. ADD COLUMN .. COMPRESSION .. ALTER TABLE col_compression ADD COLUMN c TEXT COMPRESSION pglz; SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_compression%' AND attnum > 0 ORDER BY 1 )$$); -- test attaching to a partitioned table with column compression @@ -126,7 +126,7 @@ SELECT create_distributed_table('col_comp_par', 'a'); CREATE TABLE col_comp_par_1 PARTITION OF col_comp_par FOR VALUES FROM ('abc') TO ('def'); SELECT result AS column_compression FROM run_command_on_workers($$SELECT ARRAY( -SELECT attname || ' ' || attcompression FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_comp\_par\_1\_%' AND attnum > 0 ORDER BY 1 +SELECT attname || ' ' || attcompression::text FROM pg_attribute WHERE attrelid::regclass::text LIKE 'pg14.col\_comp\_par\_1\_%' AND attnum > 0 ORDER BY 1 )$$); RESET citus.multi_shard_modify_mode; @@ -763,6 +763,22 @@ WITH ( ); SELECT create_distributed_table('compression_and_generated_col', 'rev', colocate_with:='none'); +-- See that it's enabling the binary option for logical replication +BEGIN; +SET LOCAL citus.log_remote_commands TO ON; +SET LOCAL citus.grep_remote_commands = '%CREATE SUBSCRIPTION%'; +SELECT citus_move_shard_placement(980042, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode := 'force_logical'); +ROLLBACK; + +-- See that it doesn't enable the binary option for logical replication if we +-- disable binary protocol. +BEGIN; +SET LOCAL citus.log_remote_commands TO ON; +SET LOCAL citus.grep_remote_commands = '%CREATE SUBSCRIPTION%'; +SET LOCAL citus.enable_binary_protocol = FALSE; +SELECT citus_move_shard_placement(980042, 'localhost', :worker_1_port, 'localhost', :worker_2_port, shard_transfer_mode := 'force_logical'); +ROLLBACK; + DROP TABLE compression_and_defaults, compression_and_generated_col; -- cleanup diff --git a/src/test/regress/sql/pg15.sql b/src/test/regress/sql/pg15.sql new file mode 100644 index 000000000..cefc419e8 --- /dev/null +++ b/src/test/regress/sql/pg15.sql @@ -0,0 +1,246 @@ +-- +-- PG15 +-- +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 + +CREATE SCHEMA pg15; +SET search_path TO pg15; +SET citus.next_shard_id TO 960000; +SET citus.shard_count TO 4; + +-- +-- In PG15, there is an added option to use ICU as global locale provider. +-- pg_collation has three locale-related fields: collcollate and collctype, +-- which are libc-related fields, and a new one colliculocale, which is the +-- ICU-related field. Only the libc-related fields or the ICU-related field +-- is set, never both. +-- Relevant PG commits: +-- f2553d43060edb210b36c63187d52a632448e1d2 +-- 54637508f87bd5f07fb9406bac6b08240283be3b +-- + +-- fail, needs "locale" +CREATE COLLATION german_phonebook_test (provider = icu, lc_collate = 'de-u-co-phonebk'); + +-- fail, needs "locale" +CREATE COLLATION german_phonebook_test (provider = icu, lc_collate = 'de-u-co-phonebk', lc_ctype = 'de-u-co-phonebk'); + +-- works +CREATE COLLATION german_phonebook_test (provider = icu, locale = 'de-u-co-phonebk'); + +-- with icu provider, colliculocale will be set, collcollate and collctype will be null +SELECT result FROM run_command_on_all_nodes(' + SELECT collcollate FROM pg_collation WHERE collname = ''german_phonebook_test''; +'); +SELECT result FROM run_command_on_all_nodes(' + SELECT collctype FROM pg_collation WHERE collname = ''german_phonebook_test''; +'); +SELECT result FROM run_command_on_all_nodes(' + SELECT colliculocale FROM pg_collation WHERE collname = ''german_phonebook_test''; +'); + +-- with non-icu provider, colliculocale will be null, collcollate and collctype will be set +CREATE COLLATION default_provider (provider = libc, lc_collate = "POSIX", lc_ctype = "POSIX"); + +SELECT result FROM run_command_on_all_nodes(' + SELECT collcollate FROM pg_collation WHERE collname = ''default_provider''; +'); +SELECT result FROM run_command_on_all_nodes(' + SELECT collctype FROM pg_collation WHERE collname = ''default_provider''; +'); +SELECT result FROM run_command_on_all_nodes(' + SELECT colliculocale FROM pg_collation WHERE collname = ''default_provider''; +'); + +-- +-- In PG15, Renaming triggers on partitioned tables had two problems +-- recurses to renaming the triggers on the partitions as well. +-- Here we test that distributed triggers behave the same way. +-- Relevant PG commit: +-- 80ba4bb383538a2ee846fece6a7b8da9518b6866 +-- + +SET citus.enable_unsafe_triggers TO true; + +CREATE TABLE sale( + sale_date date not null, + state_code text, + product_sku text, + units integer) + PARTITION BY list (state_code); + +ALTER TABLE sale ADD CONSTRAINT sale_pk PRIMARY KEY (state_code, sale_date); + +CREATE TABLE sale_newyork PARTITION OF sale FOR VALUES IN ('NY'); +CREATE TABLE sale_california PARTITION OF sale FOR VALUES IN ('CA'); + +CREATE TABLE record_sale( + operation_type text not null, + product_sku text, + state_code text, + units integer, + PRIMARY KEY(state_code, product_sku, operation_type, units)); + +SELECT create_distributed_table('sale', 'state_code'); +SELECT create_distributed_table('record_sale', 'state_code', colocate_with := 'sale'); + +CREATE OR REPLACE FUNCTION record_sale() +RETURNS trigger +AS $$ +BEGIN + INSERT INTO pg15.record_sale(operation_type, product_sku, state_code, units) + VALUES (TG_OP, NEW.product_sku, NEW.state_code, NEW.units); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER record_sale_trigger +AFTER INSERT OR UPDATE OR DELETE ON sale +FOR EACH ROW EXECUTE FUNCTION pg15.record_sale(); + +CREATE VIEW sale_triggers AS + SELECT tgname, tgrelid::regclass, tgenabled + FROM pg_trigger + WHERE tgrelid::regclass::text like 'sale%' + ORDER BY 1, 2; + +SELECT * FROM sale_triggers ORDER BY 1, 2; +ALTER TRIGGER "record_sale_trigger" ON "pg15"."sale" RENAME TO "new_record_sale_trigger"; +SELECT * FROM sale_triggers ORDER BY 1, 2; + +-- +-- In PG15, For GENERATED columns, all dependencies of the generation +-- expression are recorded as NORMAL dependencies of the column itself. +-- This requires CASCADE to drop generated cols with the original col. +-- Test this behavior in distributed table, specifically with +-- undistribute_table within a transaction. +-- Relevant PG Commit: cb02fcb4c95bae08adaca1202c2081cfc81a28b5 +-- + +CREATE TABLE generated_stored_ref ( + col_1 int, + col_2 int, + col_3 int generated always as (col_1+col_2) stored, + col_4 int, + col_5 int generated always as (col_4*2-col_1) stored +); + +SELECT create_reference_table ('generated_stored_ref'); + +-- populate the table +INSERT INTO generated_stored_ref (col_1, col_4) VALUES (1,2), (11,12); +INSERT INTO generated_stored_ref (col_1, col_2, col_4) VALUES (100,101,102), (200,201,202); +SELECT * FROM generated_stored_ref ORDER BY 1,2,3,4,5; + +-- fails, CASCADE must be specified +-- will test CASCADE inside the transcation +ALTER TABLE generated_stored_ref DROP COLUMN col_1; + +BEGIN; + -- drops col_1, col_3, col_5 + ALTER TABLE generated_stored_ref DROP COLUMN col_1 CASCADE; + ALTER TABLE generated_stored_ref DROP COLUMN col_4; + + -- show that undistribute_table works fine + SELECT undistribute_table('generated_stored_ref'); + INSERT INTO generated_stored_ref VALUES (5); + SELECT * FROM generated_stored_REF ORDER BY 1; +ROLLBACK; + +SELECT undistribute_table('generated_stored_ref'); + +-- +-- In PG15, there is a new command called MERGE +-- It is currently not supported for Citus tables +-- Test the behavior with various commands with Citus table types +-- Relevant PG Commit: 7103ebb7aae8ab8076b7e85f335ceb8fe799097c +-- + +CREATE TABLE tbl1 +( + x INT +); + +CREATE TABLE tbl2 +( + x INT +); + +-- 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); +RESET client_min_messages; + +-- one table is Citus local table, fails +SELECT citus_add_local_table_to_metadata('tbl1'); + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; + +SELECT undistribute_table('tbl1'); + +-- the other table is Citus local table, fails +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 +SELECT create_reference_table('tbl2'); + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; + +-- now, both are reference, still not supported +SELECT create_reference_table('tbl1'); + +MERGE INTO tbl1 USING tbl2 ON (true) +WHEN MATCHED THEN DELETE; + +-- now, both distributed, not works +SELECT undistribute_table('tbl1'); +SELECT undistribute_table('tbl2'); +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 +WITH targq AS ( + SELECT * FROM tbl2 +) +MERGE INTO tbl1 USING targq ON (true) +WHEN MATCHED THEN DELETE; + +-- crashes on beta3, fixed on 15 stable +--WITH foo AS ( +-- MERGE INTO tbl1 USING tbl2 ON (true) +-- WHEN MATCHED THEN DELETE +--) SELECT * FROM foo; + +--COPY ( +-- MERGE INTO tbl1 USING tbl2 ON (true) +-- WHEN MATCHED THEN DELETE +--) TO stdout; + +MERGE INTO tbl1 t +USING tbl2 +ON (true) +WHEN MATCHED THEN + UPDATE SET x = (SELECT count(*) FROM tbl2); + +-- Clean up +DROP SCHEMA pg15 CASCADE; diff --git a/src/test/regress/sql/pg15_json.sql b/src/test/regress/sql/pg15_json.sql new file mode 100644 index 000000000..640953ac7 --- /dev/null +++ b/src/test/regress/sql/pg15_json.sql @@ -0,0 +1,326 @@ +-- +-- PG15+ test +-- +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 + +CREATE SCHEMA pg15_json; +SET search_path TO pg15_json; + +SET citus.next_shard_id TO 1687000; + +CREATE TABLE test_table(id bigserial, value text); +SELECT create_distributed_table('test_table', 'id'); +INSERT INTO test_table (value) SELECT i::text FROM generate_series(0,100)i; + + +CREATE TABLE my_films(id bigserial, js jsonb); +SELECT create_distributed_table('my_films', 'id'); + +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas", "director" : "Woody Allen"}, + { "title" : "The Dinner Game", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo", "director" : "Akira Kurosawa" } ] } + ] }'); + +INSERT INTO my_films(js) VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ { "title" : "Bananas2", "director" : "Woody Allen"}, + { "title" : "The Dinner Game2", "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [{ "title" : "Psycho2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [{ "title" : "Vertigo2", "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [{ "title" : "Yojimbo2", "director" : "Akira Kurosawa" } ] } + ] }'); + +-- a router query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + WHERE my_films.id = 1 + ORDER BY 1,2,3,4; + +-- router query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) as sub +WHERE my_films.id = 1 ORDER BY 1,2,3,4; + +-- router query with an explicit LATEREL SUBQUERY and LIMIT +SELECT sub.* +FROM my_films, + lateral(SELECT * FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt ORDER BY id DESC LIMIT 1) as sub +WHERE my_films.id = 1 ORDER BY 1,2,3,4; + +-- set it DEBUG1 in case the plan changes +-- we can see details +SET client_min_messages TO DEBUG1; + +-- a mult-shard query +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + ORDER BY 1,2,3,4; + +-- recursively plan subqueries that has JSON_TABLE +SELECT count(*) FROM +( + SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt + LIMIT 1) as sub_with_json, test_table +WHERE test_table.id = sub_with_json.id; + + +-- multi-shard query with an explicit LATEREL SUBQUERY +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + +-- JSON_TABLE can be on the inner part of an outer joion +SELECT sub.* +FROM my_films LEFT JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) + ORDER BY 1,2,3,4; + +-- we can pushdown this correlated subquery in WHERE clause +SELECT count(*) +FROM my_films WHERE + (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000); + +-- we can pushdown this correlated subquery in SELECT clause + SELECT (SELECT count(*) > 0 + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt) +FROM my_films; + +-- multi-shard query with an explicit LATEREL SUBQUERY +-- along with other tables +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + +-- non-colocated join fails +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) JOIN test_table ON(my_films.id != test_table.id) + ORDER BY 1,2,3,4; + +-- JSON_TABLE can be in the outer part of the join +-- as long as there is a distributed table +SELECT sub.* +FROM my_films JOIN + lateral + (SELECT * + FROM JSON_TABLE (js, '$.favorites[*]' COLUMNS (id FOR ORDINALITY, + kind text PATH '$.kind', NESTED PATH '$.films[*]' + COLUMNS (title text PATH '$.title', director text PATH '$.director'))) AS jt + LIMIT 1000) AS sub ON (true) LEFT JOIN test_table ON(my_films.id = test_table.id) + ORDER BY 1,2,3,4; + +-- JSON_TABLE cannot be on the outer side of the join +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +LEFT JOIN LATERAL + (SELECT * + FROM my_films) AS foo on(foo.id = a); + + +-- JSON_TABLE cannot be on the FROM clause alone +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) as foo +WHERE b > + (SELECT count(*) + FROM my_films WHERE id = foo.a); + +-- we can recursively plan json_tables on set operations +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY)) ORDER BY id ASC LIMIT 1) +UNION +(SELECT id FROM test_table ORDER BY id ASC LIMIT 1); + +-- LIMIT in subquery not supported when json_table exists +SELECT * +FROM json_table('[{"a":10,"b":20},{"a":30,"b":40}]'::JSONB, '$[*]' + COLUMNS (id FOR ORDINALITY, column_a int4 PATH '$.a', column_b int4 PATH '$.b', a int4, b int4, c text)) +JOIN LATERAL + (SELECT * + FROM my_films WHERE json_table.id = a LIMIT 1) as foo ON (true); + +-- a little more complex query with multiple json_table +SELECT + director1 AS director, title1, kind1, title2, kind2 +FROM + my_films, + JSON_TABLE ( js, '$.favorites' AS favs COLUMNS ( + NESTED PATH '$[*]' AS films1 COLUMNS ( + kind1 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film1 COLUMNS ( + title1 text PATH '$.title', + director1 text PATH '$.director') + ), + NESTED PATH '$[*]' AS films2 COLUMNS ( + kind2 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film2 COLUMNS ( + title2 text PATH '$.title', + director2 text PATH '$.director' + ) + ) + ) + PLAN (favs INNER ((films1 INNER film1) CROSS (films2 INNER film2))) + ) AS jt + WHERE kind1 > kind2 AND director1 = director2 + ORDER BY 1,2,3,4; + +RESET client_min_messages; + +-- test some utility functions on the target list & where clause +select jsonb_path_exists(js, '$.favorites') from my_films; +select bool_and(JSON_EXISTS(js, '$.favorites.films.title')) from my_films; +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); +SELECT count(*) FROM my_films WHERE jsonb_path_exists(js, '$.favorites'); +SELECT count(*) FROM my_films WHERE JSON_EXISTS(js, '$.favorites.films.title'); + +-- check constraint with json_exists +create table user_profiles ( + id bigserial, + addresses jsonb, + anyjson jsonb, + check (json_exists( addresses, '$.main' )) +); +select create_distributed_table('user_profiles', 'id'); +insert into user_profiles (addresses) VALUES (JSON_SCALAR('1')); +insert into user_profiles (addresses) VALUES ('{"main":"value"}'); + +-- we cannot insert because WITH UNIQUE KEYS +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITH UNIQUE KEYS)); + +-- we can insert with +insert into user_profiles (addresses) VALUES (JSON ('{"main":"value", "main":"value"}' WITHOUT UNIQUE KEYS)) RETURNING *; + +TRUNCATE user_profiles; +INSERT INTO user_profiles (anyjson) VALUES ('12'), ('"abc"'), ('[1,2,3]'), ('{"a":12}'); +select anyjson, anyjson is json array as json_array, anyjson is json object as json_object, anyjson is json scalar as json_scalar, +anyjson is json with UNIQUE keys +from user_profiles WHERE anyjson IS NOT NULL ORDER BY 1; + +-- use json_query +SELECT i, + json_query('[{"x": "aaa"},{"x": "bbb"},{"x": "ccc"}]'::JSONB, '$[$i].x' passing id AS i RETURNING text omit quotes) +FROM generate_series(0, 3) i +JOIN my_films ON(id = i) ORDER BY 1; + +-- we can use JSON_TABLE in modification queries as well + +-- use log level such that we can see trace changes +SET client_min_messages TO DEBUG1; + +--the JSON_TABLE subquery is recursively planned +UPDATE test_table SET VALUE = 'XXX' FROM( +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; + +-- Subquery with JSON table can be pushed down because two distributed tables +-- in the query are joined on distribution column +UPDATE test_table SET VALUE = 'XXX' FROM ( +SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) as foo WHERE foo.id = test_table.id; + +-- we can pushdown with CTEs as well +WITH json_cte AS +(SELECT my_films.id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.id = test_table.id; + + -- we can recursively with CTEs as well +WITH json_cte AS +(SELECT my_films.id as film_id, jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + id FOR ORDINALITY, + title text PATH '$.title', + director text PATH '$.director'))) AS jt ORDER BY jt.id LIMIT 1) +UPDATE test_table SET VALUE = 'XYZ' FROM json_cte + WHERE json_cte.film_id = test_table.id; + +SET client_min_messages TO ERROR; +DROP SCHEMA pg15_json CASCADE; diff --git a/src/test/regress/sql/shard_rebalancer.sql b/src/test/regress/sql/shard_rebalancer.sql index 0d482998b..e5ef36b52 100644 --- a/src/test/regress/sql/shard_rebalancer.sql +++ b/src/test/regress/sql/shard_rebalancer.sql @@ -1417,3 +1417,27 @@ SELECT shardid FROM pg_dist_shard; SELECT rebalance_table_shards('test_with_all_shards_excluded', excluded_shard_list:='{102073, 102074, 102075, 102076}'); DROP TABLE test_with_all_shards_excluded; + +SET citus.shard_count TO 2; + +CREATE TABLE "events.Energy Added" (user_id int, time timestamp with time zone, data jsonb, PRIMARY KEY (user_id, time )) PARTITION BY RANGE ("time"); +CREATE INDEX idx_btree_hobbies ON "events.Energy Added" USING BTREE ((data->>'location')); +SELECT create_distributed_table('"events.Energy Added"', 'user_id'); +CREATE TABLE "Energy Added_17634" PARTITION OF "events.Energy Added" FOR VALUES FROM ('2018-04-13 00:00:00+00') TO ('2018-04-14 00:00:00+00'); +CREATE TABLE "Energy Added_17635" PARTITION OF "events.Energy Added" FOR VALUES FROM ('2018-04-14 00:00:00+00') TO ('2018-04-15 00:00:00+00'); + +create table colocated_t1 (a int); +select create_distributed_table('colocated_t1','a',colocate_with=>'"events.Energy Added"'); + +create table colocated_t2 (a int); +select create_distributed_table('colocated_t2','a',colocate_with=>'"events.Energy Added"'); + +create table colocated_t3 (a int); +select create_distributed_table('colocated_t3','a',colocate_with=>'"events.Energy Added"'); + +SET client_min_messages TO DEBUG4; +SELECT * FROM get_rebalance_table_shards_plan('colocated_t1', rebalance_strategy := 'by_disk_size'); +RESET client_min_messages; + +DROP TABLE "events.Energy Added", colocated_t1, colocated_t2, colocated_t3; +RESET citus.shard_count; diff --git a/src/test/regress/sql/single_node.sql b/src/test/regress/sql/single_node.sql index 09a8c9870..7bbbda895 100644 --- a/src/test/regress/sql/single_node.sql +++ b/src/test/regress/sql/single_node.sql @@ -1,3 +1,13 @@ +-- +-- SINGLE_NODE +-- +-- This test file has an alternative output because of the change in the +-- display of SQL-standard function's arguments in INSERT/SELECT in PG15. +-- The alternative output can be deleted when we drop support for PG14 +-- +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15; + CREATE SCHEMA single_node; SET search_path TO single_node; SET citus.shard_count TO 4; diff --git a/src/test/regress/sql/split_shard_release_dsm.sql b/src/test/regress/sql/split_shard_release_dsm.sql new file mode 100644 index 000000000..b8fe6bfb6 --- /dev/null +++ b/src/test/regress/sql/split_shard_release_dsm.sql @@ -0,0 +1,28 @@ +-- Test Secneario +-- 1) Setup shared memory segment by calling worker_split_shard_replication_setup. +-- 2) Redo step 1 as the earlier memory. Redoing will trigger warning as earlier memory isn't released. +-- 3) Execute worker_split_shard_release_dsm to release the dynamic shared memory segment +-- 4) Redo step 1 and expect no warning as the earlier memory is cleanedup. + +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 + +\c - - - :worker_1_port +SET search_path TO split_shard_replication_setup_schema; +SET client_min_messages TO ERROR; +SELECT count(*) FROM pg_catalog.worker_split_shard_replication_setup(ARRAY[ + ROW(1, 'id', 2, '-2147483648', '-1', :worker_1_node)::pg_catalog.split_shard_info, + ROW(1, 'id', 3, '0', '2147483647', :worker_1_node)::pg_catalog.split_shard_info + ]); + +SET client_min_messages TO WARNING; +SELECT count(*) FROM pg_catalog.worker_split_shard_replication_setup(ARRAY[ + ROW(1, 'id', 2, '-2147483648', '-1', :worker_1_node)::pg_catalog.split_shard_info, + ROW(1, 'id', 3, '0', '2147483647', :worker_1_node)::pg_catalog.split_shard_info + ]); + +SELECT pg_catalog.worker_split_shard_release_dsm(); +SELECT count(*) FROM pg_catalog.worker_split_shard_replication_setup(ARRAY[ + ROW(1, 'id', 2, '-2147483648', '-1', :worker_1_node)::pg_catalog.split_shard_info, + ROW(1, 'id', 3, '0', '2147483647', :worker_1_node)::pg_catalog.split_shard_info + ]); diff --git a/src/test/regress/sql/start_stop_metadata_sync.sql b/src/test/regress/sql/start_stop_metadata_sync.sql index 4e30cef1c..c1fed6243 100644 --- a/src/test/regress/sql/start_stop_metadata_sync.sql +++ b/src/test/regress/sql/start_stop_metadata_sync.sql @@ -1,6 +1,7 @@ CREATE SCHEMA start_stop_metadata_sync; SET search_path TO "start_stop_metadata_sync"; SET citus.next_shard_id TO 980000; +ALTER SEQUENCE pg_catalog.pg_dist_colocationid_seq RESTART 980000; SET client_min_messages TO WARNING; SET citus.shard_count TO 4; SET citus.shard_replication_factor TO 1; diff --git a/src/test/regress/sql/stat_statements.sql b/src/test/regress/sql/stat_statements.sql index 1f15a82c4..546a5aefa 100644 --- a/src/test/regress/sql/stat_statements.sql +++ b/src/test/regress/sql/stat_statements.sql @@ -4,9 +4,9 @@ -- tests citus_stat_statements functionality SHOW server_version \gset -SELECT substring(:'server_version', '\d+')::int > 13 AS server_version_above_thirteen +SELECT substring(:'server_version', '\d+')::int >= 14 AS server_version_ge_14 \gset -\if :server_version_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'on'; \endif @@ -50,7 +50,7 @@ SELECT create_distributed_table('test','a'); insert into test values(1); select query, calls from citus_stat_statements(); -\if :server_version_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'off'; \else set citus.stat_statements_track = 'none'; @@ -64,7 +64,7 @@ insert into test values(1); select query, calls from citus_stat_statements(); -\if :server_version_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'on'; \else RESET citus.stat_statements_track; @@ -290,6 +290,6 @@ DROP TABLE stat_test_text, stat_test_bigint, stat_test_bigint_other, stat_test_r DROP FUNCTION normalize_query_string(text); -\if :server_version_above_thirteen +\if :server_version_ge_14 SET compute_query_id = 'off'; \endif diff --git a/src/test/regress/sql/subquery_view.sql b/src/test/regress/sql/subquery_view.sql index 40798fccd..8f57ef5a3 100644 --- a/src/test/regress/sql/subquery_view.sql +++ b/src/test/regress/sql/subquery_view.sql @@ -427,17 +427,21 @@ SET client_min_messages TO DEFAULT; CREATE TABLE reference_table (text_col text, int_col int); SELECT create_reference_table('reference_table'); +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 reference_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$ 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$); DROP SCHEMA subquery_view CASCADE; SET search_path TO public; diff --git a/src/test/regress/sql/window_functions.sql b/src/test/regress/sql/window_functions.sql index 5c94515a9..77f353efb 100644 --- a/src/test/regress/sql/window_functions.sql +++ b/src/test/regress/sql/window_functions.sql @@ -1,6 +1,11 @@ +-- +-- 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 @@ -571,9 +576,11 @@ ORDER BY user_id, avg(value_1) DESC LIMIT 5; -- Grouping can be pushed down with aggregates even when window function can't +SELECT public.plan_without_result_lines($Q$ 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; +$Q$); -- Window function with inlined CTE WITH cte as ( diff --git a/src/test/regress/sql/worker_split_binary_copy_test.sql b/src/test/regress/sql/worker_split_binary_copy_test.sql index a47e968bd..489ff9dc4 100644 --- a/src/test/regress/sql/worker_split_binary_copy_test.sql +++ b/src/test/regress/sql/worker_split_binary_copy_test.sql @@ -160,6 +160,7 @@ SET citus.enable_binary_protocol = true; SELECT * from worker_split_copy( 81060000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81060015, -- destination shard id @@ -178,6 +179,7 @@ SELECT * from worker_split_copy( -- BEGIN: Trigger 2-way remote shard split copy. SELECT * from worker_split_copy( 81060000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81060015, -- destination shard id diff --git a/src/test/regress/sql/worker_split_copy_test.sql b/src/test/regress/sql/worker_split_copy_test.sql index b799eb305..2fac91c69 100644 --- a/src/test/regress/sql/worker_split_copy_test.sql +++ b/src/test/regress/sql/worker_split_copy_test.sql @@ -38,6 +38,7 @@ SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \ -- BEGIN: Test Negative scenario SELECT * from worker_split_copy( 101, -- Invalid source shard id. + 'id', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id @@ -54,26 +55,31 @@ SELECT * from worker_split_copy( SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[] -- empty array ); SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[NULL] -- empty array ); SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[NULL::pg_catalog.split_copy_info]-- empty array ); SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[ROW(NULL)]-- empty array ); SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[ROW(NULL, NULL, NULL, NULL)::pg_catalog.split_copy_info] -- empty array ); -- END: Test Negative scenario @@ -83,6 +89,7 @@ SELECT * from worker_split_copy( SET citus.enable_binary_protocol = false; SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'id', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id diff --git a/src/test/regress/sql/worker_split_text_copy_test.sql b/src/test/regress/sql/worker_split_text_copy_test.sql index 10791a66d..fe2a614b3 100644 --- a/src/test/regress/sql/worker_split_text_copy_test.sql +++ b/src/test/regress/sql/worker_split_text_copy_test.sql @@ -152,6 +152,7 @@ SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \ SET citus.enable_binary_protocol = false; SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id @@ -170,6 +171,7 @@ SELECT * from worker_split_copy( -- BEGIN: Trigger 2-way remote shard split copy. SELECT * from worker_split_copy( 81070000, -- source shard id to copy + 'l_orderkey', ARRAY[ -- split copy info for split children 1 ROW(81070015, -- destination shard id