PG17 Compatibility: Support MERGE features in Citus with clean exceptions (#7781)

- Adapted `pgmerge.sql` tests from PostgreSQL community's `merge.sql` to
Citus by converting tables into Citus local tables.
- Identified two new PostgreSQL 17 MERGE features (`RETURNING` support
and MERGE on updatable views) not yet supported by Citus.
- Implemented changes to detect unsupported features and raise clean
exceptions, ensuring pgmerge tests pass without diffs.
- Addressed breaking changes caused by `MERGE ... WHEN NOT MATCHED BY
SOURCE` restructuring, reducing diffs in pgmerge tests.
- Segregated unsupported test cases into `merge_unsupported.sql` to
maintain clarity and avoid large diffs in test files.
- Prepared the Citus MERGE planner to handle new PostgreSQL changes,
reducing remaining test discrepancies.

All merge tests now pass cleanly, with unsupported cases clearly
isolated.

Relevant PG commits:
c649fa24a
https://github.com/postgres/postgres/commit/c649fa24a
0294df2f1
https://github.com/postgres/postgres/commit/0294df2f1
---------

Co-authored-by: naisila <nicypp@gmail.com>
pull/7922/head
Teja Mupparti 2024-12-19 03:02:24 -08:00 committed by naisila
parent 088731e9db
commit 35d1160ace
13 changed files with 415 additions and 104 deletions

View File

@ -346,6 +346,7 @@ static LocalCopyStatus GetLocalCopyStatus(void);
static bool ShardIntervalListHasLocalPlacements(List *shardIntervalList); static bool ShardIntervalListHasLocalPlacements(List *shardIntervalList);
static void LogLocalCopyToRelationExecution(uint64 shardId); static void LogLocalCopyToRelationExecution(uint64 shardId);
static void LogLocalCopyToFileExecution(uint64 shardId); static void LogLocalCopyToFileExecution(uint64 shardId);
static void ErrorIfMergeInCopy(CopyStmt *copyStatement);
/* exports for SQL callable functions */ /* exports for SQL callable functions */
@ -2823,6 +2824,32 @@ CopyStatementHasFormat(CopyStmt *copyStatement, char *formatName)
} }
/*
* ErrorIfMergeInCopy Raises an exception if the MERGE is called in the COPY
* where Citus tables are involved, as we don't support this yet
* Relevant PG17 commit: c649fa24a
*/
static void
ErrorIfMergeInCopy(CopyStmt *copyStatement)
{
#if PG_VERSION_NUM < 170000
return;
#else
if (!copyStatement->relation && (IsA(copyStatement->query, MergeStmt)))
{
/*
* This path is currently not reachable because Merge in COPY can
* only work with a RETURNING clause, and a RETURNING check
* will error out sooner for Citus
*/
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MERGE with Citus tables "
"is not yet supported in COPY")));
}
#endif
}
/* /*
* ProcessCopyStmt handles Citus specific concerns for COPY like supporting * ProcessCopyStmt handles Citus specific concerns for COPY like supporting
* COPYing from distributed tables and preventing unsupported actions. The * COPYing from distributed tables and preventing unsupported actions. The
@ -2860,6 +2887,8 @@ ProcessCopyStmt(CopyStmt *copyStatement, QueryCompletion *completionTag, const
*/ */
if (copyStatement->relation != NULL) if (copyStatement->relation != NULL)
{ {
ErrorIfMergeInCopy(copyStatement);
bool isFrom = copyStatement->is_from; bool isFrom = copyStatement->is_from;
/* consider using RangeVarGetRelidExtended to check perms before locking */ /* consider using RangeVarGetRelidExtended to check perms before locking */

View File

@ -97,6 +97,8 @@ static DistributedPlan * CreateNonPushableMergePlan(Oid targetRelationId, uint64
plannerRestrictionContext, plannerRestrictionContext,
ParamListInfo boundParams); ParamListInfo boundParams);
static char * MergeCommandResultIdPrefix(uint64 planId); static char * MergeCommandResultIdPrefix(uint64 planId);
static void ErrorIfMergeHasReturningList(Query *query);
static Node * GetMergeJoinCondition(Query *mergeQuery);
#endif #endif
@ -156,8 +158,51 @@ CreateMergePlan(uint64 planId, Query *originalQuery, Query *query,
} }
/*
* GetMergeJoinTree constructs and returns the jointree for a MERGE query.
*/
FromExpr *
GetMergeJoinTree(Query *mergeQuery)
{
FromExpr *mergeJointree = NULL;
#if PG_VERSION_NUM >= PG_VERSION_17
/*
* In Postgres 17, the query tree has a specific field for the merge condition.
* For deriving the WhereClauseList from the merge condition, we construct a dummy
* jointree with an empty fromlist. This works because the fromlist of a merge query
* join tree consists of range table references only, and range table references are
* disregarded by the WhereClauseList() walker.
* Relevant PG17 commit: 0294df2f1
*/
mergeJointree = makeFromExpr(NIL, mergeQuery->mergeJoinCondition);
#else
mergeJointree = mergeQuery->jointree;
#endif
return mergeJointree;
}
#if PG_VERSION_NUM >= PG_VERSION_15 #if PG_VERSION_NUM >= PG_VERSION_15
/*
* GetMergeJoinCondition returns the quals of the ON condition
*/
static Node *
GetMergeJoinCondition(Query *mergeQuery)
{
Node *joinCondition = NULL;
#if PG_VERSION_NUM >= PG_VERSION_17
joinCondition = (Node *) mergeQuery->mergeJoinCondition;
#else
joinCondition = (Node *) mergeQuery->jointree->quals;
#endif
return joinCondition;
}
/* /*
* CreateRouterMergePlan attempts to create a pushable plan for the given MERGE * CreateRouterMergePlan attempts to create a pushable plan for the given MERGE
* SQL statement. If the planning fails, the ->planningError is set to a description * SQL statement. If the planning fails, the ->planningError is set to a description
@ -562,7 +607,7 @@ MergeQualAndTargetListFunctionsSupported(Oid resultRelationId, Query *query,
List *targetList, CmdType commandType) List *targetList, CmdType commandType)
{ {
uint32 targetRangeTableIndex = query->resultRelation; uint32 targetRangeTableIndex = query->resultRelation;
FromExpr *joinTree = query->jointree; FromExpr *joinTree = GetMergeJoinTree(query);
Var *distributionColumn = NULL; Var *distributionColumn = NULL;
if (IsCitusTable(resultRelationId) && HasDistributionKey(resultRelationId)) if (IsCitusTable(resultRelationId) && HasDistributionKey(resultRelationId))
{ {
@ -722,8 +767,9 @@ ErrorIfRepartitionMergeNotSupported(Oid targetRelationId, Query *mergeQuery,
/* /*
* Sub-queries and CTEs are not allowed in actions and ON clause * Sub-queries and CTEs are not allowed in actions and ON clause
*/ */
if (FindNodeMatchingCheckFunction((Node *) mergeQuery->jointree->quals, Node *joinCondition = GetMergeJoinCondition(mergeQuery);
IsNodeSubquery))
if (FindNodeMatchingCheckFunction(joinCondition, IsNodeSubquery))
{ {
ereport(ERROR, ereport(ERROR,
(errmsg("Sub-queries and CTEs are not allowed in ON clause for MERGE " (errmsg("Sub-queries and CTEs are not allowed in ON clause for MERGE "
@ -949,9 +995,26 @@ ConvertSourceRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte,
} }
/*
* ErrorIfMergeHasReturningList raises an exception if the MERGE has
* a RETURNING clause, as we don't support this yet for Citus tables
* Relevant PG17 commit: c649fa24a
*/
static void
ErrorIfMergeHasReturningList(Query *query)
{
if (query->returningList)
{
ereport(ERROR, (errmsg("MERGE with RETURNING is not yet supported "
"for Citus tables")));
}
}
/* /*
* ErrorIfMergeNotSupported Checks for conditions that are not supported in either * ErrorIfMergeNotSupported Checks for conditions that are not supported in either
* the routable or repartition strategies. It checks for * the routable or repartition strategies. It checks for
* - MERGE with a RETURNING clause
* - Supported table types and their combinations * - Supported table types and their combinations
* - Check the target lists and quals of both the query and merge actions * - Check the target lists and quals of both the query and merge actions
* - Supported CTEs * - Supported CTEs
@ -959,6 +1022,7 @@ ConvertSourceRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte,
static void static void
ErrorIfMergeNotSupported(Query *query, Oid targetRelationId, List *rangeTableList) ErrorIfMergeNotSupported(Query *query, Oid targetRelationId, List *rangeTableList)
{ {
ErrorIfMergeHasReturningList(query);
ErrorIfMergeHasUnsupportedTables(targetRelationId, rangeTableList); ErrorIfMergeHasUnsupportedTables(targetRelationId, rangeTableList);
ErrorIfMergeQueryQualAndTargetListNotSupported(targetRelationId, query); ErrorIfMergeQueryQualAndTargetListNotSupported(targetRelationId, query);
ErrorIfUnsupportedCTEs(query); ErrorIfUnsupportedCTEs(query);
@ -1207,12 +1271,15 @@ ErrorIfMergeQueryQualAndTargetListNotSupported(Oid targetRelationId, Query *orig
"supported in MERGE sql with distributed tables"))); "supported in MERGE sql with distributed tables")));
} }
Node *joinCondition = GetMergeJoinCondition(originalQuery);
DeferredErrorMessage *deferredError = DeferredErrorMessage *deferredError =
MergeQualAndTargetListFunctionsSupported(targetRelationId, MergeQualAndTargetListFunctionsSupported(
originalQuery, targetRelationId,
originalQuery->jointree->quals, originalQuery,
originalQuery->targetList, joinCondition,
originalQuery->commandType); originalQuery->targetList,
originalQuery->commandType);
if (deferredError) if (deferredError)
{ {
@ -1286,8 +1353,7 @@ static int
SourceResultPartitionColumnIndex(Query *mergeQuery, List *sourceTargetList, SourceResultPartitionColumnIndex(Query *mergeQuery, List *sourceTargetList,
CitusTableCacheEntry *targetRelation) CitusTableCacheEntry *targetRelation)
{ {
/* Get all the Join conditions from the ON clause */ List *mergeJoinConditionList = WhereClauseList(GetMergeJoinTree(mergeQuery));
List *mergeJoinConditionList = WhereClauseList(mergeQuery->jointree);
Var *targetColumn = targetRelation->partitionColumn; Var *targetColumn = targetRelation->partitionColumn;
Var *sourceRepartitionVar = NULL; Var *sourceRepartitionVar = NULL;
bool foundTypeMismatch = false; bool foundTypeMismatch = false;

View File

@ -598,8 +598,7 @@ TargetlistAndFunctionsSupported(Oid resultRelationId, FromExpr *joinTree, Node *
} }
if (commandType == CMD_UPDATE && targetEntryPartitionColumn && if (commandType == CMD_UPDATE && targetEntryPartitionColumn &&
TargetEntryChangesValue(targetEntry, partitionColumn, TargetEntryChangesValue(targetEntry, partitionColumn, joinTree))
joinTree))
{ {
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
"modifying the partition value of rows is not " "modifying the partition value of rows is not "

View File

@ -32,6 +32,7 @@ extern void NonPushableMergeCommandExplainScan(CustomScanState *node, List *ance
struct ExplainState *es); struct ExplainState *es);
extern Var * FetchAndValidateInsertVarIfExists(Oid targetRelationId, Query *query); extern Var * FetchAndValidateInsertVarIfExists(Oid targetRelationId, Query *query);
extern RangeTblEntry * ExtractMergeSourceRangeTableEntry(Query *query, bool joinSourceOk); extern RangeTblEntry * ExtractMergeSourceRangeTableEntry(Query *query, bool joinSourceOk);
extern FromExpr * GetMergeJoinTree(Query *mergeQuery);
#endif /* MERGE_PLANNER_H */ #endif /* MERGE_PLANNER_H */

View File

@ -0,0 +1,101 @@
SHOW server_version \gset
SELECT CASE
WHEN substring(current_setting('server_version'), '\d+')::int >= 17 THEN '17+'
WHEN substring(current_setting('server_version'), '\d+')::int IN (15, 16) THEN '15_16'
WHEN substring(current_setting('server_version'), '\d+')::int = 14 THEN '14'
ELSE 'Unsupported version'
END AS version_category;
version_category
---------------------------------------------------------------------
17+
(1 row)
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
\gset
\if :server_version_ge_15
\else
\q
\endif
--
-- MERGE test from PG community (adapted to Citus by converting all tables to Citus local)
--
DROP SCHEMA IF EXISTS pgmerge_schema CASCADE;
NOTICE: schema "pgmerge_schema" does not exist, skipping
CREATE SCHEMA pgmerge_schema;
SET search_path TO pgmerge_schema;
SET citus.use_citus_managed_tables to true;
CREATE TABLE target (tid integer, balance integer)
WITH (autovacuum_enabled=off);
CREATE TABLE source (sid integer, delta integer) -- no index
WITH (autovacuum_enabled=off);
\set SHOW_CONTEXT errors
-- used in a CTE
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
ERROR: WITH query "foo" does not have a RETURNING clause
-- used in COPY
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) TO stdout;
ERROR: COPY query must have a RETURNING clause
-- used in a CTE with RETURNING
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE RETURNING target.*
) SELECT * FROM foo;
ERROR: MERGE with RETURNING is not yet supported for Citus tables
-- used in COPY with RETURNING
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE RETURNING target.*
) TO stdout;
ERROR: MERGE with RETURNING is not yet supported for Citus tables
-- unsupported relation types
-- view
CREATE VIEW tv AS SELECT count(tid) AS tid FROM target;
MERGE INTO tv t
USING source s
ON t.tid = s.sid
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES;
ERROR: cannot insert into view "tv"
DETAIL: Views that return aggregate functions are not automatically updatable.
HINT: To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.
DROP VIEW tv;
CREATE TABLE sq_target (tid integer NOT NULL, balance integer)
WITH (autovacuum_enabled=off);
CREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)
WITH (autovacuum_enabled=off);
SELECT citus_add_local_table_to_metadata('sq_target');
citus_add_local_table_to_metadata
---------------------------------------------------------------------
(1 row)
SELECT citus_add_local_table_to_metadata('sq_source');
citus_add_local_table_to_metadata
---------------------------------------------------------------------
(1 row)
INSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);
INSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);
CREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;
-- RETURNING
BEGIN;
INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t
USING v
ON tid = sid
WHEN MATCHED AND tid > 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING *;
ERROR: MERGE with RETURNING is not yet supported for Citus tables
ROLLBACK;

View File

@ -0,0 +1,100 @@
SHOW server_version \gset
SELECT CASE
WHEN substring(current_setting('server_version'), '\d+')::int >= 17 THEN '17+'
WHEN substring(current_setting('server_version'), '\d+')::int IN (15, 16) THEN '15_16'
WHEN substring(current_setting('server_version'), '\d+')::int = 14 THEN '14'
ELSE 'Unsupported version'
END AS version_category;
version_category
---------------------------------------------------------------------
15_16
(1 row)
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
\gset
\if :server_version_ge_15
\else
\q
\endif
--
-- MERGE test from PG community (adapted to Citus by converting all tables to Citus local)
--
DROP SCHEMA IF EXISTS pgmerge_schema CASCADE;
NOTICE: schema "pgmerge_schema" does not exist, skipping
CREATE SCHEMA pgmerge_schema;
SET search_path TO pgmerge_schema;
SET citus.use_citus_managed_tables to true;
CREATE TABLE target (tid integer, balance integer)
WITH (autovacuum_enabled=off);
CREATE TABLE source (sid integer, delta integer) -- no index
WITH (autovacuum_enabled=off);
\set SHOW_CONTEXT errors
-- used in a CTE
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
ERROR: MERGE not supported in WITH query
-- used in COPY
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) TO stdout;
ERROR: MERGE not supported in COPY
-- used in a CTE with RETURNING
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE RETURNING target.*
) SELECT * FROM foo;
ERROR: syntax error at or near "RETURNING"
-- used in COPY with RETURNING
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE RETURNING target.*
) TO stdout;
ERROR: syntax error at or near "RETURNING"
-- unsupported relation types
-- view
CREATE VIEW tv AS SELECT count(tid) AS tid FROM target;
MERGE INTO tv t
USING source s
ON t.tid = s.sid
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES;
ERROR: cannot execute MERGE on relation "tv"
DETAIL: This operation is not supported for views.
DROP VIEW tv;
CREATE TABLE sq_target (tid integer NOT NULL, balance integer)
WITH (autovacuum_enabled=off);
CREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)
WITH (autovacuum_enabled=off);
SELECT citus_add_local_table_to_metadata('sq_target');
citus_add_local_table_to_metadata
---------------------------------------------------------------------
(1 row)
SELECT citus_add_local_table_to_metadata('sq_source');
citus_add_local_table_to_metadata
---------------------------------------------------------------------
(1 row)
INSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);
INSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);
CREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;
-- RETURNING
BEGIN;
INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t
USING v
ON tid = sid
WHEN MATCHED AND tid > 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING *;
ERROR: syntax error at or near "RETURNING"
ROLLBACK;

View File

@ -0,0 +1,17 @@
SHOW server_version \gset
SELECT CASE
WHEN substring(current_setting('server_version'), '\d+')::int >= 17 THEN '17+'
WHEN substring(current_setting('server_version'), '\d+')::int IN (15, 16) THEN '15_16'
WHEN substring(current_setting('server_version'), '\d+')::int = 14 THEN '14'
ELSE 'Unsupported version'
END AS version_category;
version_category
---------------------------------------------------------------------
14
(1 row)
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
\gset
\if :server_version_ge_15
\else
\q

View File

@ -437,16 +437,6 @@ MERGE INTO tbl1 USING targq ON (true)
WHEN MATCHED THEN DELETE; WHEN MATCHED THEN DELETE;
ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from a non-equi-join. ERROR: The required join operation is missing between the target's distribution column and any expression originating from the source. The issue may arise from a non-equi-join.
DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting DETAIL: Without a equi-join condition on the target's distribution column, the source rows cannot be efficiently redistributed, and the NOT-MATCHED condition cannot be evaluated unambiguously. This can result in incorrect or unexpected results when attempting to merge tables in a distributed setting
WITH foo AS (
MERGE INTO tbl1 USING tbl2 ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
ERROR: MERGE not supported in WITH query
COPY (
MERGE INTO tbl1 USING tbl2 ON (true)
WHEN MATCHED THEN DELETE
) TO stdout;
ERROR: MERGE not supported in COPY
MERGE INTO tbl1 t MERGE INTO tbl1 t
USING tbl2 USING tbl2
ON (true) ON (true)

View File

@ -162,29 +162,7 @@ ON tid = tid
WHEN MATCHED THEN DO NOTHING; WHEN MATCHED THEN DO NOTHING;
ERROR: name "target" specified more than once ERROR: name "target" specified more than once
DETAIL: The name is used both as MERGE target table and data source. DETAIL: The name is used both as MERGE target table and data source.
-- used in a CTE
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
ERROR: MERGE not supported in WITH query
-- used in COPY
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) TO stdout;
ERROR: MERGE not supported in COPY
-- unsupported relation types -- unsupported relation types
-- view
CREATE VIEW tv AS SELECT * FROM target;
MERGE INTO tv t
USING source s
ON t.tid = s.sid
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES;
ERROR: cannot execute MERGE on relation "tv"
DETAIL: This operation is not supported for views.
DROP VIEW tv;
-- materialized view -- materialized view
CREATE MATERIALIZED VIEW mv AS SELECT * FROM target; CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
MERGE INTO mv t MERGE INTO mv t
@ -1376,21 +1354,6 @@ WHEN NOT MATCHED THEN
WHEN MATCHED AND tid < 2 THEN WHEN MATCHED AND tid < 2 THEN
DELETE; DELETE;
ROLLBACK; ROLLBACK;
-- RETURNING
BEGIN;
INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t
USING v
ON tid = sid
WHEN MATCHED AND tid > 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING *;
ERROR: syntax error at or near "RETURNING"
ROLLBACK;
-- EXPLAIN -- EXPLAIN
CREATE TABLE ex_mtarget (a int, b int) CREATE TABLE ex_mtarget (a int, b int)
WITH (autovacuum_enabled=off); WITH (autovacuum_enabled=off);

View File

@ -120,7 +120,7 @@ test: merge pgmerge
test: merge_repartition2 test: merge_repartition2
test: merge_repartition1 merge_schema_sharding test: merge_repartition1 merge_schema_sharding
test: merge_partition_tables test: merge_partition_tables
test: merge_vcore test: merge_vcore merge_unsupported
# --------- # ---------
# test that no tests leaked intermediate results. This should always be last # test that no tests leaked intermediate results. This should always be last

View File

@ -0,0 +1,89 @@
SHOW server_version \gset
SELECT CASE
WHEN substring(current_setting('server_version'), '\d+')::int >= 17 THEN '17+'
WHEN substring(current_setting('server_version'), '\d+')::int IN (15, 16) THEN '15_16'
WHEN substring(current_setting('server_version'), '\d+')::int = 14 THEN '14'
ELSE 'Unsupported version'
END AS version_category;
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
\gset
\if :server_version_ge_15
\else
\q
\endif
--
-- MERGE test from PG community (adapted to Citus by converting all tables to Citus local)
--
DROP SCHEMA IF EXISTS pgmerge_schema CASCADE;
CREATE SCHEMA pgmerge_schema;
SET search_path TO pgmerge_schema;
SET citus.use_citus_managed_tables to true;
CREATE TABLE target (tid integer, balance integer)
WITH (autovacuum_enabled=off);
CREATE TABLE source (sid integer, delta integer) -- no index
WITH (autovacuum_enabled=off);
\set SHOW_CONTEXT errors
-- used in a CTE
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
-- used in COPY
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) TO stdout;
-- used in a CTE with RETURNING
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE RETURNING target.*
) SELECT * FROM foo;
-- used in COPY with RETURNING
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE RETURNING target.*
) TO stdout;
-- unsupported relation types
-- view
CREATE VIEW tv AS SELECT count(tid) AS tid FROM target;
MERGE INTO tv t
USING source s
ON t.tid = s.sid
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES;
DROP VIEW tv;
CREATE TABLE sq_target (tid integer NOT NULL, balance integer)
WITH (autovacuum_enabled=off);
CREATE TABLE sq_source (delta integer, sid integer, balance integer DEFAULT 0)
WITH (autovacuum_enabled=off);
SELECT citus_add_local_table_to_metadata('sq_target');
SELECT citus_add_local_table_to_metadata('sq_source');
INSERT INTO sq_target(tid, balance) VALUES (1,100), (2,200), (3,300);
INSERT INTO sq_source(sid, delta) VALUES (1,10), (2,20), (4,40);
CREATE VIEW v AS SELECT * FROM sq_source WHERE sid < 2;
-- RETURNING
BEGIN;
INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t
USING v
ON tid = sid
WHEN MATCHED AND tid > 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING *;
ROLLBACK;

View File

@ -287,16 +287,6 @@ WITH targq AS (
MERGE INTO tbl1 USING targq ON (true) MERGE INTO tbl1 USING targq ON (true)
WHEN MATCHED THEN DELETE; WHEN MATCHED THEN DELETE;
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 MERGE INTO tbl1 t
USING tbl2 USING tbl2
ON (true) ON (true)

View File

@ -116,27 +116,8 @@ MERGE INTO target
USING target USING target
ON tid = tid ON tid = tid
WHEN MATCHED THEN DO NOTHING; WHEN MATCHED THEN DO NOTHING;
-- used in a CTE
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
-- used in COPY
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) TO stdout;
-- unsupported relation types -- unsupported relation types
-- view
CREATE VIEW tv AS SELECT * FROM target;
MERGE INTO tv t
USING source s
ON t.tid = s.sid
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES;
DROP VIEW tv;
-- materialized view -- materialized view
CREATE MATERIALIZED VIEW mv AS SELECT * FROM target; CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
MERGE INTO mv t MERGE INTO mv t
@ -905,21 +886,6 @@ WHEN MATCHED AND tid < 2 THEN
DELETE; DELETE;
ROLLBACK; ROLLBACK;
-- RETURNING
BEGIN;
INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t
USING v
ON tid = sid
WHEN MATCHED AND tid > 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING *;
ROLLBACK;
-- EXPLAIN -- EXPLAIN
CREATE TABLE ex_mtarget (a int, b int) CREATE TABLE ex_mtarget (a int, b int)
WITH (autovacuum_enabled=off); WITH (autovacuum_enabled=off);