mirror of https://github.com/citusdata/citus.git
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
parent
088731e9db
commit
35d1160ace
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 "
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue