mirror of https://github.com/citusdata/citus.git
Support MERGE Phase – I
All the tables (target, source or any CTE present) in the SQL statement are local i.e. a merge-sql with a combination of Citus local and Non-Citus tables (regular Postgres tables) should work and give the same result as Postgres MERGE on regular tables. Catch and throw an exception (not-yet-supported) for all other scenarios during Citus-planning phase.pull/6567/head
parent
5268d0a6cb
commit
9a9989fc15
|
@ -368,6 +368,8 @@ static void get_insert_query_def(Query *query, deparse_context *context,
|
||||||
bool colNamesVisible);
|
bool colNamesVisible);
|
||||||
static void get_update_query_def(Query *query, deparse_context *context,
|
static void get_update_query_def(Query *query, deparse_context *context,
|
||||||
bool colNamesVisible);
|
bool colNamesVisible);
|
||||||
|
static void get_merge_query_def(Query *query, deparse_context *context);
|
||||||
|
|
||||||
static void get_update_query_targetlist_def(Query *query, List *targetList,
|
static void get_update_query_targetlist_def(Query *query, List *targetList,
|
||||||
deparse_context *context,
|
deparse_context *context,
|
||||||
RangeTblEntry *rte);
|
RangeTblEntry *rte);
|
||||||
|
@ -459,6 +461,7 @@ static char *generate_function_name(Oid funcid, int nargs,
|
||||||
List *argnames, Oid *argtypes,
|
List *argnames, Oid *argtypes,
|
||||||
bool has_variadic, bool *use_variadic_p,
|
bool has_variadic, bool *use_variadic_p,
|
||||||
ParseExprKind special_exprkind);
|
ParseExprKind special_exprkind);
|
||||||
|
static List *get_insert_column_names_list(List *targetList, StringInfo buf, deparse_context *context, RangeTblEntry *rte);
|
||||||
|
|
||||||
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
|
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
|
||||||
|
|
||||||
|
@ -2095,6 +2098,10 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
||||||
get_delete_query_def(query, &context, colNamesVisible);
|
get_delete_query_def(query, &context, colNamesVisible);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CMD_MERGE:
|
||||||
|
get_merge_query_def(query, &context);
|
||||||
|
break;
|
||||||
|
|
||||||
case CMD_NOTHING:
|
case CMD_NOTHING:
|
||||||
appendStringInfoString(buf, "NOTHING");
|
appendStringInfoString(buf, "NOTHING");
|
||||||
break;
|
break;
|
||||||
|
@ -3225,9 +3232,8 @@ get_insert_query_def(Query *query, deparse_context *context,
|
||||||
RangeTblEntry *select_rte = NULL;
|
RangeTblEntry *select_rte = NULL;
|
||||||
RangeTblEntry *values_rte = NULL;
|
RangeTblEntry *values_rte = NULL;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
char *sep;
|
|
||||||
ListCell *l;
|
ListCell *l;
|
||||||
List *strippedexprs;
|
List *strippedexprs = NIL;
|
||||||
|
|
||||||
/* Insert the WITH clause if given */
|
/* Insert the WITH clause if given */
|
||||||
get_with_clause(query, context);
|
get_with_clause(query, context);
|
||||||
|
@ -3281,43 +3287,11 @@ get_insert_query_def(Query *query, deparse_context *context,
|
||||||
* Add the insert-column-names list. Any indirection decoration needed on
|
* Add the insert-column-names list. Any indirection decoration needed on
|
||||||
* the column names can be inferred from the top targetlist.
|
* the column names can be inferred from the top targetlist.
|
||||||
*/
|
*/
|
||||||
strippedexprs = NIL;
|
|
||||||
sep = "";
|
|
||||||
if (query->targetList)
|
if (query->targetList)
|
||||||
appendStringInfoChar(buf, '(');
|
|
||||||
foreach(l, query->targetList)
|
|
||||||
{
|
{
|
||||||
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
strippedexprs = get_insert_column_names_list(query->targetList,
|
||||||
|
buf, context, rte);
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -3741,6 +3715,148 @@ get_delete_query_def(Query *query, deparse_context *context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----------
|
||||||
|
* get_merge_query_def - Parse back a MERGE parsetree
|
||||||
|
* ----------
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
get_merge_query_def(Query *query, deparse_context *context)
|
||||||
|
{
|
||||||
|
StringInfo buf = context->buf;
|
||||||
|
RangeTblEntry *targetRte;
|
||||||
|
|
||||||
|
/* Insert the WITH clause if given */
|
||||||
|
get_with_clause(query, context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start the query with MERGE INTO <target>
|
||||||
|
*/
|
||||||
|
targetRte = 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(targetRte) == CITUS_RTE_SHARD)
|
||||||
|
{
|
||||||
|
char *fragmentSchemaName = NULL;
|
||||||
|
char *fragmentTableName = NULL;
|
||||||
|
|
||||||
|
ExtractRangeTblExtraData(targetRte, NULL, &fragmentSchemaName, &fragmentTableName, NULL);
|
||||||
|
|
||||||
|
/* use schema and table name from the remote alias */
|
||||||
|
appendStringInfo(buf, "MERGE INTO %s%s",
|
||||||
|
only_marker(targetRte),
|
||||||
|
generate_fragment_name(fragmentSchemaName, fragmentTableName));
|
||||||
|
|
||||||
|
if(targetRte->eref != NULL)
|
||||||
|
appendStringInfo(buf, " %s",
|
||||||
|
quote_identifier(get_rtable_name(query->resultRelation, context)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "MERGE INTO %s%s",
|
||||||
|
only_marker(targetRte),
|
||||||
|
generate_relation_or_shard_name(targetRte->relid,
|
||||||
|
context->distrelid,
|
||||||
|
context->shardid, NIL));
|
||||||
|
|
||||||
|
if (targetRte->alias != NULL)
|
||||||
|
appendStringInfo(buf, " %s",
|
||||||
|
quote_identifier(get_rtable_name(query->resultRelation, context)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the MERGE source relation -- USING <source>
|
||||||
|
*/
|
||||||
|
get_from_clause(query, " USING ", context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the MERGE ON condition
|
||||||
|
*/
|
||||||
|
Assert(query->jointree->quals != NULL);
|
||||||
|
{
|
||||||
|
appendContextKeyword(context, " ON ",
|
||||||
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
|
get_rule_expr(query->jointree->quals, context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListCell *actionCell = NULL;
|
||||||
|
foreach(actionCell, query->mergeActionList)
|
||||||
|
{
|
||||||
|
MergeAction *action = (MergeAction *) lfirst(actionCell);
|
||||||
|
|
||||||
|
/* Add WHEN [NOT] MATCHED */
|
||||||
|
appendContextKeyword(context, " WHEN",
|
||||||
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
|
appendStringInfo(buf, " %s", action->matched ? "MATCHED" : "NOT MATCHED");
|
||||||
|
|
||||||
|
/* Add optional AND <condition> */
|
||||||
|
if (action->qual)
|
||||||
|
{
|
||||||
|
appendContextKeyword(context, " AND ",
|
||||||
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
|
get_rule_expr(action->qual, context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendContextKeyword(context, " THEN",
|
||||||
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
|
|
||||||
|
switch (action->commandType)
|
||||||
|
{
|
||||||
|
case CMD_INSERT:
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " INSERT " );
|
||||||
|
List *strippedexprs = NIL;
|
||||||
|
|
||||||
|
if (action->targetList)
|
||||||
|
{
|
||||||
|
strippedexprs = get_insert_column_names_list(action->targetList,
|
||||||
|
buf, context, targetRte);
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_UPDATE:
|
||||||
|
appendStringInfo(buf, " UPDATE SET " );
|
||||||
|
get_update_query_targetlist_def(query, action->targetList,
|
||||||
|
context, targetRte);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_DELETE:
|
||||||
|
appendStringInfo(buf, " DELETE" );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_NOTHING:
|
||||||
|
appendStringInfo(buf, " DO NOTHING " );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
elog(ERROR, "unknown action in MERGE WHEN clause");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ereport(DEBUG1, (errmsg("<Deparsed MERGE query: %s>", buf->data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* get_utility_query_def - Parse back a UTILITY parsetree
|
* get_utility_query_def - Parse back a UTILITY parsetree
|
||||||
* ----------
|
* ----------
|
||||||
|
@ -8761,4 +8877,54 @@ getOwnedSequences_internal(Oid relid, AttrNumber attnum, char deptype)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_insert_column_names_list Prepares the insert-column-names list. Any indirection
|
||||||
|
* decoration needed on the column names can be inferred from the top targetlist.
|
||||||
|
*/
|
||||||
|
static List *
|
||||||
|
get_insert_column_names_list(List *targetList, StringInfo buf,
|
||||||
|
deparse_context *context, RangeTblEntry *rte)
|
||||||
|
{
|
||||||
|
char *sep;
|
||||||
|
ListCell *l;
|
||||||
|
List *strippedexprs;
|
||||||
|
|
||||||
|
strippedexprs = NIL;
|
||||||
|
sep = "";
|
||||||
|
appendStringInfoChar(buf, '(');
|
||||||
|
foreach(l, 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));
|
||||||
|
}
|
||||||
|
appendStringInfoString(buf, ") ");
|
||||||
|
|
||||||
|
return strippedexprs;
|
||||||
|
}
|
||||||
#endif /* (PG_VERSION_NUM >= PG_VERSION_15) && (PG_VERSION_NUM < PG_VERSION_16) */
|
#endif /* (PG_VERSION_NUM >= PG_VERSION_15) && (PG_VERSION_NUM < PG_VERSION_16) */
|
||||||
|
|
|
@ -78,7 +78,7 @@ RebuildQueryStrings(Job *workerJob)
|
||||||
query = copyObject(originalQuery);
|
query = copyObject(originalQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UpdateOrDeleteQuery(query))
|
if (UpdateOrDeleteOrMergeQuery(query))
|
||||||
{
|
{
|
||||||
List *relationShardList = task->relationShardList;
|
List *relationShardList = task->relationShardList;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "distributed/citus_nodefuncs.h"
|
#include "distributed/citus_nodefuncs.h"
|
||||||
#include "distributed/citus_nodes.h"
|
#include "distributed/citus_nodes.h"
|
||||||
#include "distributed/citus_ruleutils.h"
|
#include "distributed/citus_ruleutils.h"
|
||||||
|
#include "distributed/colocation_utils.h"
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
#include "distributed/cte_inline.h"
|
#include "distributed/cte_inline.h"
|
||||||
#include "distributed/function_call_delegation.h"
|
#include "distributed/function_call_delegation.h"
|
||||||
|
@ -74,7 +75,8 @@ static uint64 NextPlanId = 1;
|
||||||
/* keep track of planner call stack levels */
|
/* keep track of planner call stack levels */
|
||||||
int PlannerLevel = 0;
|
int PlannerLevel = 0;
|
||||||
|
|
||||||
static void ErrorIfQueryHasMergeCommand(Query *queryTree);
|
static void ErrorIfQueryHasUnsupportedMergeCommand(Query *queryTree,
|
||||||
|
List *rangeTableList);
|
||||||
static bool ContainsMergeCommandWalker(Node *node);
|
static bool ContainsMergeCommandWalker(Node *node);
|
||||||
static bool ListContainsDistributedTableRTE(List *rangeTableList,
|
static bool ListContainsDistributedTableRTE(List *rangeTableList,
|
||||||
bool *maybeHasForeignDistributedTable);
|
bool *maybeHasForeignDistributedTable);
|
||||||
|
@ -130,7 +132,7 @@ static PlannedStmt * PlanDistributedStmt(DistributedPlanningContext *planContext
|
||||||
static RTEListProperties * GetRTEListProperties(List *rangeTableList);
|
static RTEListProperties * GetRTEListProperties(List *rangeTableList);
|
||||||
static List * TranslatedVars(PlannerInfo *root, int relationIndex);
|
static List * TranslatedVars(PlannerInfo *root, int relationIndex);
|
||||||
static void WarnIfListHasForeignDistributedTable(List *rangeTableList);
|
static void WarnIfListHasForeignDistributedTable(List *rangeTableList);
|
||||||
|
static void ErrorIfMergeHasUnsupportedTables(Query *parse, List *rangeTableList);
|
||||||
|
|
||||||
/* Distributed planner hook */
|
/* Distributed planner hook */
|
||||||
PlannedStmt *
|
PlannedStmt *
|
||||||
|
@ -202,7 +204,7 @@ distributed_planner(Query *parse,
|
||||||
* Fast path queries cannot have merge command, and we
|
* Fast path queries cannot have merge command, and we
|
||||||
* prevent the remaining here.
|
* prevent the remaining here.
|
||||||
*/
|
*/
|
||||||
ErrorIfQueryHasMergeCommand(parse);
|
ErrorIfQueryHasUnsupportedMergeCommand(parse, rangeTableList);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When there are partitioned tables (not applicable to fast path),
|
* When there are partitioned tables (not applicable to fast path),
|
||||||
|
@ -303,11 +305,13 @@ distributed_planner(Query *parse,
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ErrorIfQueryHasMergeCommand walks over the query tree and throws error
|
* ErrorIfQueryHasUnsupportedMergeCommand walks over the query tree and bails out
|
||||||
* if there are any Merge command (e.g., CMD_MERGE) in the query tree.
|
* if there is no Merge command (e.g., CMD_MERGE) in the query tree. For merge,
|
||||||
|
* looks for all supported combinations, throws an exception if any violations
|
||||||
|
* are seen.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
ErrorIfQueryHasMergeCommand(Query *queryTree)
|
ErrorIfQueryHasUnsupportedMergeCommand(Query *queryTree, List *rangeTableList)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Postgres currently doesn't support Merge queries inside subqueries and
|
* Postgres currently doesn't support Merge queries inside subqueries and
|
||||||
|
@ -316,11 +320,20 @@ ErrorIfQueryHasMergeCommand(Query *queryTree)
|
||||||
* We do not call this path for fast-path queries to avoid this additional
|
* We do not call this path for fast-path queries to avoid this additional
|
||||||
* overhead.
|
* overhead.
|
||||||
*/
|
*/
|
||||||
if (ContainsMergeCommandWalker((Node *) queryTree))
|
if (!ContainsMergeCommandWalker((Node *) queryTree))
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
/* No MERGE found */
|
||||||
errmsg("MERGE command is not supported on Citus tables yet")));
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In Citus we have limited support for MERGE, it's allowed
|
||||||
|
* only if all the tables(target, source or any CTE) tables
|
||||||
|
* are are local i.e. a combination of Citus local and Non-Citus
|
||||||
|
* tables (regular Postgres tables).
|
||||||
|
*/
|
||||||
|
ErrorIfMergeHasUnsupportedTables(queryTree, rangeTableList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -331,7 +344,10 @@ ErrorIfQueryHasMergeCommand(Query *queryTree)
|
||||||
static bool
|
static bool
|
||||||
ContainsMergeCommandWalker(Node *node)
|
ContainsMergeCommandWalker(Node *node)
|
||||||
{
|
{
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
#if PG_VERSION_NUM < PG_VERSION_15
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (node == NULL)
|
if (node == NULL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -340,7 +356,7 @@ ContainsMergeCommandWalker(Node *node)
|
||||||
if (IsA(node, Query))
|
if (IsA(node, Query))
|
||||||
{
|
{
|
||||||
Query *query = (Query *) node;
|
Query *query = (Query *) node;
|
||||||
if (query->commandType == CMD_MERGE)
|
if (IsMergeQuery(query))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -349,7 +365,6 @@ ContainsMergeCommandWalker(Node *node)
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression_tree_walker(node, ContainsMergeCommandWalker, NULL);
|
return expression_tree_walker(node, ContainsMergeCommandWalker, NULL);
|
||||||
#endif
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -628,7 +643,7 @@ IsModifyCommand(Query *query)
|
||||||
CmdType commandType = query->commandType;
|
CmdType commandType = query->commandType;
|
||||||
|
|
||||||
if (commandType == CMD_INSERT || commandType == CMD_UPDATE ||
|
if (commandType == CMD_INSERT || commandType == CMD_UPDATE ||
|
||||||
commandType == CMD_DELETE)
|
commandType == CMD_DELETE || commandType == CMD_MERGE)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2592,3 +2607,148 @@ WarnIfListHasForeignDistributedTable(List *rangeTableList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IsMergeAllowedOnRelation takes a relation entry and checks if MERGE command is
|
||||||
|
* permitted on special relations, such as materialized view, returns true only if
|
||||||
|
* it's a "source" relation.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
IsMergeAllowedOnRelation(Query *parse, RangeTblEntry *rte)
|
||||||
|
{
|
||||||
|
if (!IsMergeQuery(parse))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeTblEntry *targetRte = rt_fetch(parse->resultRelation, parse->rtable);
|
||||||
|
|
||||||
|
/* Is it a target relation? */
|
||||||
|
if (targetRte->relid == rte->relid)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ErrorIfMergeHasUnsupportedTables checks if all the tables(target, source or any CTE
|
||||||
|
* present) in the MERGE command are local i.e. a combination of Citus local and Non-Citus
|
||||||
|
* tables (regular Postgres tables), raises an exception for all other combinations.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
ErrorIfMergeHasUnsupportedTables(Query *parse, List *rangeTableList)
|
||||||
|
{
|
||||||
|
ListCell *tableCell = NULL;
|
||||||
|
|
||||||
|
foreach(tableCell, rangeTableList)
|
||||||
|
{
|
||||||
|
RangeTblEntry *rangeTableEntry = (RangeTblEntry *) lfirst(tableCell);
|
||||||
|
Oid relationId = rangeTableEntry->relid;
|
||||||
|
|
||||||
|
switch (rangeTableEntry->rtekind)
|
||||||
|
{
|
||||||
|
case RTE_RELATION:
|
||||||
|
{
|
||||||
|
/* Check the relation type */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RTE_SUBQUERY:
|
||||||
|
case RTE_FUNCTION:
|
||||||
|
case RTE_TABLEFUNC:
|
||||||
|
case RTE_VALUES:
|
||||||
|
case RTE_JOIN:
|
||||||
|
case RTE_CTE:
|
||||||
|
{
|
||||||
|
/* Skip them as base table(s) will be checked */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RTE_NAMEDTUPLESTORE is typically used in ephmeral named relations,
|
||||||
|
* such as, trigger data; until we find a genuine use case, raise an
|
||||||
|
* exception.
|
||||||
|
* RTE_RESULT is a node added by the planner and we shouldn't
|
||||||
|
* encounter it in the parse tree.
|
||||||
|
*/
|
||||||
|
case RTE_NAMEDTUPLESTORE:
|
||||||
|
case RTE_RESULT:
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("MERGE command is not supported with "
|
||||||
|
"Tuplestores and results")));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("MERGE command: Unrecognized range table entry.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RTE Relation can be of various types, check them now */
|
||||||
|
|
||||||
|
/* skip the regular views as they are replaced with subqueries */
|
||||||
|
if (rangeTableEntry->relkind == RELKIND_VIEW)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeTableEntry->relkind == RELKIND_MATVIEW ||
|
||||||
|
rangeTableEntry->relkind == RELKIND_FOREIGN_TABLE)
|
||||||
|
{
|
||||||
|
/* Materialized view or Foreign table as target is not allowed */
|
||||||
|
if (IsMergeAllowedOnRelation(parse, rangeTableEntry))
|
||||||
|
{
|
||||||
|
/* Non target relation is ok */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("MERGE command is not allowed "
|
||||||
|
"on materialized view")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeTableEntry->relkind != RELKIND_RELATION &&
|
||||||
|
rangeTableEntry->relkind != RELKIND_PARTITIONED_TABLE)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("Unexpected relation type(relkind:%c) in MERGE command",
|
||||||
|
rangeTableEntry->relkind)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(rangeTableEntry->relid != 0);
|
||||||
|
|
||||||
|
/* Distributed tables and Reference tables are not supported yet */
|
||||||
|
if (IsCitusTableType(relationId, REFERENCE_TABLE) ||
|
||||||
|
IsCitusTableType(relationId, DISTRIBUTED_TABLE))
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("MERGE command is not supported on "
|
||||||
|
"distributed/reference tables yet")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Regular Postgres tables and Citus local tables are allowed */
|
||||||
|
if (!IsCitusTable(relationId) ||
|
||||||
|
IsCitusTableType(relationId, CITUS_LOCAL_TABLE))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Any other Citus table type missing ? */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All the tables are local, supported */
|
||||||
|
}
|
||||||
|
|
|
@ -162,16 +162,11 @@ FastPathRouterQuery(Query *query, Node **distributionKeyValue)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
if (IsMergeQuery(query))
|
||||||
if (query->commandType == CMD_MERGE)
|
|
||||||
{
|
{
|
||||||
/*
|
/* MERGE command is not a fast path query */
|
||||||
* Citus doesn't support MERGE command, lets return
|
|
||||||
* early and explicitly for fast-path queries.
|
|
||||||
*/
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We want to deal with only very simple queries. Some of the
|
* We want to deal with only very simple queries. Some of the
|
||||||
|
|
|
@ -2464,7 +2464,7 @@ QueryPushdownTaskCreate(Query *originalQuery, int shardIndex,
|
||||||
* If it is a modify query with sub-select, we need to set result relation shard's id
|
* If it is a modify query with sub-select, we need to set result relation shard's id
|
||||||
* as anchor shard id.
|
* as anchor shard id.
|
||||||
*/
|
*/
|
||||||
if (UpdateOrDeleteQuery(originalQuery))
|
if (UpdateOrDeleteOrMergeQuery(originalQuery))
|
||||||
{
|
{
|
||||||
resultRangeTable = rt_fetch(originalQuery->resultRelation, originalQuery->rtable);
|
resultRangeTable = rt_fetch(originalQuery->resultRelation, originalQuery->rtable);
|
||||||
resultRelationOid = resultRangeTable->relid;
|
resultRelationOid = resultRangeTable->relid;
|
||||||
|
@ -2493,7 +2493,7 @@ QueryPushdownTaskCreate(Query *originalQuery, int shardIndex,
|
||||||
anchorShardId = shardInterval->shardId;
|
anchorShardId = shardInterval->shardId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (UpdateOrDeleteQuery(originalQuery))
|
else if (UpdateOrDeleteOrMergeQuery(originalQuery))
|
||||||
{
|
{
|
||||||
shardInterval = cacheEntry->sortedShardIntervalArray[shardIndex];
|
shardInterval = cacheEntry->sortedShardIntervalArray[shardIndex];
|
||||||
if (!modifyWithSubselect || relationId == resultRelationOid)
|
if (!modifyWithSubselect || relationId == resultRelationOid)
|
||||||
|
|
|
@ -127,8 +127,9 @@ static DeferredErrorMessage * ModifyPartialQuerySupported(Query *queryTree, bool
|
||||||
multiShardQuery,
|
multiShardQuery,
|
||||||
Oid *distributedTableId);
|
Oid *distributedTableId);
|
||||||
static bool NodeIsFieldStore(Node *node);
|
static bool NodeIsFieldStore(Node *node);
|
||||||
static DeferredErrorMessage * MultiShardUpdateDeleteSupported(Query *originalQuery,
|
static DeferredErrorMessage * MultiShardUpdateDeleteMergeSupported(Query *originalQuery,
|
||||||
PlannerRestrictionContext *
|
PlannerRestrictionContext
|
||||||
|
*
|
||||||
plannerRestrictionContext);
|
plannerRestrictionContext);
|
||||||
static DeferredErrorMessage * SingleShardUpdateDeleteSupported(Query *originalQuery,
|
static DeferredErrorMessage * SingleShardUpdateDeleteSupported(Query *originalQuery,
|
||||||
PlannerRestrictionContext *
|
PlannerRestrictionContext *
|
||||||
|
@ -233,7 +234,7 @@ CreateModifyPlan(Query *originalQuery, Query *query,
|
||||||
return distributedPlan;
|
return distributedPlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UpdateOrDeleteQuery(query))
|
if (UpdateOrDeleteOrMergeQuery(query))
|
||||||
{
|
{
|
||||||
job = RouterJob(originalQuery, plannerRestrictionContext,
|
job = RouterJob(originalQuery, plannerRestrictionContext,
|
||||||
&distributedPlan->planningError);
|
&distributedPlan->planningError);
|
||||||
|
@ -539,7 +540,7 @@ ModifyPartialQuerySupported(Query *queryTree, bool multiShardQuery,
|
||||||
if (queryTree->hasSubLinks == true)
|
if (queryTree->hasSubLinks == true)
|
||||||
{
|
{
|
||||||
/* we support subqueries for INSERTs only via INSERT INTO ... SELECT */
|
/* we support subqueries for INSERTs only via INSERT INTO ... SELECT */
|
||||||
if (!UpdateOrDeleteQuery(queryTree))
|
if (!UpdateOrDeleteOrMergeQuery(queryTree))
|
||||||
{
|
{
|
||||||
Assert(queryTree->commandType == CMD_INSERT);
|
Assert(queryTree->commandType == CMD_INSERT);
|
||||||
|
|
||||||
|
@ -953,11 +954,19 @@ ModifyQuerySupported(Query *queryTree, Query *originalQuery, bool multiShardQuer
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
else if (rangeTableEntry->relkind == RELKIND_MATVIEW)
|
else if (rangeTableEntry->relkind == RELKIND_MATVIEW)
|
||||||
|
{
|
||||||
|
if (IsMergeAllowedOnRelation(originalQuery, rangeTableEntry))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
||||||
"materialized views in modify queries are not supported",
|
"materialized views in "
|
||||||
|
"modify queries are not supported",
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/* for other kinds of relations, check if its distributed */
|
/* for other kinds of relations, check if its distributed */
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -995,10 +1004,10 @@ ModifyQuerySupported(Query *queryTree, Query *originalQuery, bool multiShardQuer
|
||||||
char *rangeTableEntryErrorDetail = NULL;
|
char *rangeTableEntryErrorDetail = NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We support UPDATE and DELETE with subqueries and joins unless
|
* We support UPDATE, DELETE and MERGE with subqueries and joins unless
|
||||||
* they are multi shard queries.
|
* they are multi shard queries.
|
||||||
*/
|
*/
|
||||||
if (UpdateOrDeleteQuery(queryTree))
|
if (UpdateOrDeleteOrMergeQuery(queryTree))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1059,7 +1068,8 @@ ModifyQuerySupported(Query *queryTree, Query *originalQuery, bool multiShardQuer
|
||||||
|
|
||||||
if (multiShardQuery)
|
if (multiShardQuery)
|
||||||
{
|
{
|
||||||
errorMessage = MultiShardUpdateDeleteSupported(originalQuery,
|
errorMessage = MultiShardUpdateDeleteMergeSupported(
|
||||||
|
originalQuery,
|
||||||
plannerRestrictionContext);
|
plannerRestrictionContext);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1239,11 +1249,11 @@ ErrorIfOnConflictNotSupported(Query *queryTree)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MultiShardUpdateDeleteSupported returns the error message if the update/delete is
|
* MultiShardUpdateDeleteMergeSupported returns the error message if the update/delete is
|
||||||
* not pushdownable, otherwise it returns NULL.
|
* not pushdownable, otherwise it returns NULL.
|
||||||
*/
|
*/
|
||||||
static DeferredErrorMessage *
|
static DeferredErrorMessage *
|
||||||
MultiShardUpdateDeleteSupported(Query *originalQuery,
|
MultiShardUpdateDeleteMergeSupported(Query *originalQuery,
|
||||||
PlannerRestrictionContext *plannerRestrictionContext)
|
PlannerRestrictionContext *plannerRestrictionContext)
|
||||||
{
|
{
|
||||||
DeferredErrorMessage *errorMessage = NULL;
|
DeferredErrorMessage *errorMessage = NULL;
|
||||||
|
@ -1382,14 +1392,25 @@ HasDangerousJoinUsing(List *rtableList, Node *joinTreeNode)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UpdateOrDeleteQuery checks if the given query is an UPDATE or DELETE command.
|
* UpdateOrDeleteOrMergeQuery checks if the given query is an UPDATE or DELETE or
|
||||||
* If it is, it returns true otherwise it returns false.
|
* MERGE command. If it is, it returns true otherwise it returns false.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
UpdateOrDeleteQuery(Query *query)
|
UpdateOrDeleteOrMergeQuery(Query *query)
|
||||||
{
|
{
|
||||||
return query->commandType == CMD_UPDATE ||
|
return (query->commandType == CMD_UPDATE ||
|
||||||
query->commandType == CMD_DELETE;
|
query->commandType == CMD_DELETE ||
|
||||||
|
query->commandType == CMD_MERGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IsMergeQuery checks if the given query is a MERGE SQL command.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
IsMergeQuery(Query *query)
|
||||||
|
{
|
||||||
|
return (query->commandType == CMD_MERGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1826,16 +1847,28 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*planningError)
|
if (*planningError)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* For MERGE, we do _not_ plan anything other than Router job, let's
|
||||||
|
* not continue further down the lane in distributed planning, simply
|
||||||
|
* bail out.
|
||||||
|
*/
|
||||||
|
if (IsMergeQuery(originalQuery))
|
||||||
|
{
|
||||||
|
RaiseDeferredError(*planningError, ERROR);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Job *job = CreateJob(originalQuery);
|
Job *job = CreateJob(originalQuery);
|
||||||
job->partitionKeyValue = partitionKeyValue;
|
job->partitionKeyValue = partitionKeyValue;
|
||||||
|
|
||||||
if (originalQuery->resultRelation > 0)
|
if (originalQuery->resultRelation > 0)
|
||||||
{
|
{
|
||||||
RangeTblEntry *updateOrDeleteRTE = ExtractResultRelationRTE(originalQuery);
|
RangeTblEntry *updateOrDeleteOrMergeRTE = ExtractResultRelationRTE(originalQuery);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If all of the shards are pruned, we replace the relation RTE into
|
* If all of the shards are pruned, we replace the relation RTE into
|
||||||
|
@ -1844,7 +1877,7 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
|
||||||
* DELETE RTE with subquery type, we just set task list to empty and return
|
* DELETE RTE with subquery type, we just set task list to empty and return
|
||||||
* the job.
|
* the job.
|
||||||
*/
|
*/
|
||||||
if (updateOrDeleteRTE->rtekind == RTE_SUBQUERY)
|
if (updateOrDeleteOrMergeRTE->rtekind == RTE_SUBQUERY)
|
||||||
{
|
{
|
||||||
job->taskList = NIL;
|
job->taskList = NIL;
|
||||||
return job;
|
return job;
|
||||||
|
@ -2250,7 +2283,7 @@ PlanRouterQuery(Query *originalQuery,
|
||||||
* We defer error here, later the planner is forced to use a generic plan
|
* We defer error here, later the planner is forced to use a generic plan
|
||||||
* by assigning arbitrarily high cost to the plan.
|
* by assigning arbitrarily high cost to the plan.
|
||||||
*/
|
*/
|
||||||
if (UpdateOrDeleteQuery(originalQuery) && isMultiShardQuery)
|
if (UpdateOrDeleteOrMergeQuery(originalQuery) && isMultiShardQuery)
|
||||||
{
|
{
|
||||||
planningError = DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
planningError = DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
||||||
"Router planner cannot handle multi-shard "
|
"Router planner cannot handle multi-shard "
|
||||||
|
@ -2289,7 +2322,7 @@ PlanRouterQuery(Query *originalQuery,
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert(UpdateOrDeleteQuery(originalQuery));
|
Assert(UpdateOrDeleteOrMergeQuery(originalQuery));
|
||||||
planningError = ModifyQuerySupported(originalQuery, originalQuery,
|
planningError = ModifyQuerySupported(originalQuery, originalQuery,
|
||||||
isMultiShardQuery,
|
isMultiShardQuery,
|
||||||
plannerRestrictionContext);
|
plannerRestrictionContext);
|
||||||
|
@ -2361,7 +2394,7 @@ PlanRouterQuery(Query *originalQuery,
|
||||||
* If this is an UPDATE or DELETE query which requires coordinator evaluation,
|
* If this is an UPDATE or DELETE query which requires coordinator evaluation,
|
||||||
* don't try update shard names, and postpone that to execution phase.
|
* don't try update shard names, and postpone that to execution phase.
|
||||||
*/
|
*/
|
||||||
bool isUpdateOrDelete = UpdateOrDeleteQuery(originalQuery);
|
bool isUpdateOrDelete = UpdateOrDeleteOrMergeQuery(originalQuery);
|
||||||
if (!(isUpdateOrDelete && RequiresCoordinatorEvaluation(originalQuery)))
|
if (!(isUpdateOrDelete && RequiresCoordinatorEvaluation(originalQuery)))
|
||||||
{
|
{
|
||||||
UpdateRelationToShardNames((Node *) originalQuery, *relationShardList);
|
UpdateRelationToShardNames((Node *) originalQuery, *relationShardList);
|
||||||
|
|
|
@ -29,6 +29,11 @@
|
||||||
|
|
||||||
#define CURSOR_OPT_FORCE_DISTRIBUTED 0x080000
|
#define CURSOR_OPT_FORCE_DISTRIBUTED 0x080000
|
||||||
|
|
||||||
|
/* Hack to compile Citus on pre-MERGE Postgres versions */
|
||||||
|
#if PG_VERSION_NUM < PG_VERSION_15
|
||||||
|
#define CMD_MERGE CMD_UNKNOWN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* level of planner calls */
|
/* level of planner calls */
|
||||||
extern int PlannerLevel;
|
extern int PlannerLevel;
|
||||||
|
@ -248,4 +253,6 @@ extern struct DistributedPlan * CreateDistributedPlan(uint64 planId, Query *orig
|
||||||
PlannerRestrictionContext *
|
PlannerRestrictionContext *
|
||||||
plannerRestrictionContext);
|
plannerRestrictionContext);
|
||||||
|
|
||||||
|
extern bool IsMergeAllowedOnRelation(Query *parse, RangeTblEntry *rte);
|
||||||
|
|
||||||
#endif /* DISTRIBUTED_PLANNER_H */
|
#endif /* DISTRIBUTED_PLANNER_H */
|
||||||
|
|
|
@ -76,7 +76,8 @@ extern RangeTblEntry * ExtractResultRelationRTEOrError(Query *query);
|
||||||
extern RangeTblEntry * ExtractDistributedInsertValuesRTE(Query *query);
|
extern RangeTblEntry * ExtractDistributedInsertValuesRTE(Query *query);
|
||||||
extern bool IsMultiRowInsert(Query *query);
|
extern bool IsMultiRowInsert(Query *query);
|
||||||
extern void AddPartitionKeyNotNullFilterToSelect(Query *subqery);
|
extern void AddPartitionKeyNotNullFilterToSelect(Query *subqery);
|
||||||
extern bool UpdateOrDeleteQuery(Query *query);
|
extern bool UpdateOrDeleteOrMergeQuery(Query *query);
|
||||||
|
extern bool IsMergeQuery(Query *query);
|
||||||
|
|
||||||
extern uint64 GetAnchorShardId(List *relationShardList);
|
extern uint64 GetAnchorShardId(List *relationShardList);
|
||||||
extern List * TargetShardIntervalForFastPathQuery(Query *query,
|
extern List * TargetShardIntervalForFastPathQuery(Query *query,
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_15
|
||||||
|
\else
|
||||||
|
\q
|
|
@ -254,7 +254,7 @@ NOTICE: renaming the new table to pg15.generated_stored_ref
|
||||||
|
|
||||||
--
|
--
|
||||||
-- In PG15, there is a new command called MERGE
|
-- In PG15, there is a new command called MERGE
|
||||||
-- It is currently not supported for Citus tables
|
-- It is currently not supported for Citus non-local tables
|
||||||
-- Test the behavior with various commands with Citus table types
|
-- Test the behavior with various commands with Citus table types
|
||||||
-- Relevant PG Commit: 7103ebb7aae8ab8076b7e85f335ceb8fe799097c
|
-- Relevant PG Commit: 7103ebb7aae8ab8076b7e85f335ceb8fe799097c
|
||||||
--
|
--
|
||||||
|
@ -287,7 +287,6 @@ SELECT citus_add_local_table_to_metadata('tbl1');
|
||||||
|
|
||||||
MERGE INTO tbl1 USING tbl2 ON (true)
|
MERGE INTO tbl1 USING tbl2 ON (true)
|
||||||
WHEN MATCHED THEN DELETE;
|
WHEN MATCHED THEN DELETE;
|
||||||
ERROR: MERGE command is not supported on Citus tables yet
|
|
||||||
SELECT undistribute_table('tbl1');
|
SELECT undistribute_table('tbl1');
|
||||||
NOTICE: creating a new table for pg15.tbl1
|
NOTICE: creating a new table for pg15.tbl1
|
||||||
NOTICE: moving the data of pg15.tbl1
|
NOTICE: moving the data of pg15.tbl1
|
||||||
|
@ -307,7 +306,6 @@ SELECT citus_add_local_table_to_metadata('tbl2');
|
||||||
|
|
||||||
MERGE INTO tbl1 USING tbl2 ON (true)
|
MERGE INTO tbl1 USING tbl2 ON (true)
|
||||||
WHEN MATCHED THEN DELETE;
|
WHEN MATCHED THEN DELETE;
|
||||||
ERROR: MERGE command is not supported on Citus tables yet
|
|
||||||
-- one table is reference, the other local, not supported
|
-- one table is reference, the other local, not supported
|
||||||
SELECT create_reference_table('tbl2');
|
SELECT create_reference_table('tbl2');
|
||||||
create_reference_table
|
create_reference_table
|
||||||
|
@ -317,7 +315,7 @@ SELECT create_reference_table('tbl2');
|
||||||
|
|
||||||
MERGE INTO tbl1 USING tbl2 ON (true)
|
MERGE INTO tbl1 USING tbl2 ON (true)
|
||||||
WHEN MATCHED THEN DELETE;
|
WHEN MATCHED THEN DELETE;
|
||||||
ERROR: MERGE command is not supported on Citus tables yet
|
ERROR: MERGE command is not supported on distributed/reference tables yet
|
||||||
-- now, both are reference, still not supported
|
-- now, both are reference, still not supported
|
||||||
SELECT create_reference_table('tbl1');
|
SELECT create_reference_table('tbl1');
|
||||||
create_reference_table
|
create_reference_table
|
||||||
|
@ -327,7 +325,7 @@ SELECT create_reference_table('tbl1');
|
||||||
|
|
||||||
MERGE INTO tbl1 USING tbl2 ON (true)
|
MERGE INTO tbl1 USING tbl2 ON (true)
|
||||||
WHEN MATCHED THEN DELETE;
|
WHEN MATCHED THEN DELETE;
|
||||||
ERROR: MERGE command is not supported on Citus tables yet
|
ERROR: MERGE command is not supported on distributed/reference tables yet
|
||||||
-- now, both distributed, not works
|
-- now, both distributed, not works
|
||||||
SELECT undistribute_table('tbl1');
|
SELECT undistribute_table('tbl1');
|
||||||
NOTICE: creating a new table for pg15.tbl1
|
NOTICE: creating a new table for pg15.tbl1
|
||||||
|
@ -421,14 +419,14 @@ SELECT create_distributed_table('tbl2', 'x');
|
||||||
|
|
||||||
MERGE INTO tbl1 USING tbl2 ON (true)
|
MERGE INTO tbl1 USING tbl2 ON (true)
|
||||||
WHEN MATCHED THEN DELETE;
|
WHEN MATCHED THEN DELETE;
|
||||||
ERROR: MERGE command is not supported on Citus tables yet
|
ERROR: MERGE command is not supported on distributed/reference tables yet
|
||||||
-- also, not inside subqueries & ctes
|
-- also, not inside subqueries & ctes
|
||||||
WITH targq AS (
|
WITH targq AS (
|
||||||
SELECT * FROM tbl2
|
SELECT * FROM tbl2
|
||||||
)
|
)
|
||||||
MERGE INTO tbl1 USING targq ON (true)
|
MERGE INTO tbl1 USING targq ON (true)
|
||||||
WHEN MATCHED THEN DELETE;
|
WHEN MATCHED THEN DELETE;
|
||||||
ERROR: MERGE command is not supported on Citus tables yet
|
ERROR: MERGE command is not supported on distributed/reference tables yet
|
||||||
-- crashes on beta3, fixed on 15 stable
|
-- crashes on beta3, fixed on 15 stable
|
||||||
--WITH foo AS (
|
--WITH foo AS (
|
||||||
-- MERGE INTO tbl1 USING tbl2 ON (true)
|
-- MERGE INTO tbl1 USING tbl2 ON (true)
|
||||||
|
@ -443,7 +441,7 @@ USING tbl2
|
||||||
ON (true)
|
ON (true)
|
||||||
WHEN MATCHED THEN
|
WHEN MATCHED THEN
|
||||||
UPDATE SET x = (SELECT count(*) FROM tbl2);
|
UPDATE SET x = (SELECT count(*) FROM tbl2);
|
||||||
ERROR: MERGE command is not supported on Citus tables yet
|
ERROR: MERGE command is not supported on distributed/reference tables yet
|
||||||
-- test numeric types with negative scale
|
-- test numeric types with negative scale
|
||||||
CREATE TABLE numeric_negative_scale(numeric_column numeric(3,-1), orig_value int);
|
CREATE TABLE numeric_negative_scale(numeric_column numeric(3,-1), orig_value int);
|
||||||
INSERT into numeric_negative_scale SELECT x,x FROM generate_series(111, 115) x;
|
INSERT into numeric_negative_scale SELECT x,x FROM generate_series(111, 115) x;
|
||||||
|
@ -1490,3 +1488,5 @@ SELECT run_command_on_workers($$DROP ACCESS METHOD heap2$$);
|
||||||
\set VERBOSITY terse
|
\set VERBOSITY terse
|
||||||
SET client_min_messages TO ERROR;
|
SET client_min_messages TO ERROR;
|
||||||
DROP SCHEMA pg15 CASCADE;
|
DROP SCHEMA pg15 CASCADE;
|
||||||
|
DROP ROLE rls_tenant_1;
|
||||||
|
DROP ROLE rls_tenant_2;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_15
|
||||||
|
\else
|
||||||
|
\q
|
|
@ -104,6 +104,10 @@ test: background_task_queue_monitor
|
||||||
# Causal clock test
|
# Causal clock test
|
||||||
test: clock
|
test: clock
|
||||||
|
|
||||||
|
# MERGE tests
|
||||||
|
test: merge
|
||||||
|
test: pgmerge
|
||||||
|
|
||||||
# ---------
|
# ---------
|
||||||
# test that no tests leaked intermediate results. This should always be last
|
# test that no tests leaked intermediate results. This should always be last
|
||||||
# ---------
|
# ---------
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -160,7 +160,7 @@ SELECT undistribute_table('generated_stored_ref');
|
||||||
|
|
||||||
--
|
--
|
||||||
-- In PG15, there is a new command called MERGE
|
-- In PG15, there is a new command called MERGE
|
||||||
-- It is currently not supported for Citus tables
|
-- It is currently not supported for Citus non-local tables
|
||||||
-- Test the behavior with various commands with Citus table types
|
-- Test the behavior with various commands with Citus table types
|
||||||
-- Relevant PG Commit: 7103ebb7aae8ab8076b7e85f335ceb8fe799097c
|
-- Relevant PG Commit: 7103ebb7aae8ab8076b7e85f335ceb8fe799097c
|
||||||
--
|
--
|
||||||
|
@ -943,3 +943,5 @@ SELECT run_command_on_workers($$DROP ACCESS METHOD heap2$$);
|
||||||
\set VERBOSITY terse
|
\set VERBOSITY terse
|
||||||
SET client_min_messages TO ERROR;
|
SET client_min_messages TO ERROR;
|
||||||
DROP SCHEMA pg15 CASCADE;
|
DROP SCHEMA pg15 CASCADE;
|
||||||
|
DROP ROLE rls_tenant_1;
|
||||||
|
DROP ROLE rls_tenant_2;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue