Add ruleutils support for decompiling MERGE commands.

This was overlooked when MERGE was added, but it's essential
support for MERGE in new-style SQL functions.

Alvaro Herrera

Discussion: https://postgr.es/m/3579737.1683293801@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2023-05-07 11:01:15 -04:00
parent 58f5edf849
commit 41e2c52fd6
3 changed files with 262 additions and 0 deletions

View File

@ -411,6 +411,8 @@ static void get_update_query_targetlist_def(Query *query, List *targetList,
RangeTblEntry *rte);
static void get_delete_query_def(Query *query, deparse_context *context,
bool colNamesVisible);
static void get_merge_query_def(Query *query, deparse_context *context,
bool colNamesVisible);
static void get_utility_query_def(Query *query, deparse_context *context);
static void get_basic_select_query(Query *query, deparse_context *context,
TupleDesc resultDesc, bool colNamesVisible);
@ -5448,6 +5450,10 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
get_delete_query_def(query, &context, colNamesVisible);
break;
case CMD_MERGE:
get_merge_query_def(query, &context, colNamesVisible);
break;
case CMD_NOTHING:
appendStringInfoString(buf, "NOTHING");
break;
@ -7044,6 +7050,128 @@ 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,
bool colNamesVisible)
{
StringInfo buf = context->buf;
RangeTblEntry *rte;
ListCell *lc;
/* Insert the WITH clause if given */
get_with_clause(query, context);
/*
* Start the query with MERGE INTO relname
*/
rte = rt_fetch(query->resultRelation, query->rtable);
Assert(rte->rtekind == RTE_RELATION);
if (PRETTY_INDENT(context))
{
appendStringInfoChar(buf, ' ');
context->indentLevel += PRETTYINDENT_STD;
}
appendStringInfo(buf, "MERGE INTO %s%s",
only_marker(rte),
generate_relation_name(rte->relid, NIL));
/* Print the relation alias, if needed */
get_rte_alias(rte, query->resultRelation, false, context);
/* Print the source relation and join clause */
get_from_clause(query, " USING ", context);
appendContextKeyword(context, " ON ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
get_rule_expr(query->jointree->quals, context, false);
/* Print each merge action */
foreach(lc, query->mergeActionList)
{
MergeAction *action = lfirst_node(MergeAction, lc);
appendContextKeyword(context, " WHEN ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
appendStringInfo(buf, "%sMATCHED", action->matched ? "" : "NOT ");
if (action->qual)
{
appendContextKeyword(context, " AND ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
get_rule_expr(action->qual, context, false);
}
appendContextKeyword(context, " THEN ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
if (action->commandType == CMD_INSERT)
{
/* This generally matches get_insert_query_def() */
List *strippedexprs = NIL;
const char *sep = "";
ListCell *lc2;
appendStringInfoString(buf, "INSERT");
if (action->targetList)
appendStringInfoString(buf, " (");
foreach(lc2, action->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc2);
Assert(!tle->resjunk);
appendStringInfoString(buf, sep);
sep = ", ";
appendStringInfoString(buf,
quote_identifier(get_attname(rte->relid,
tle->resno,
false)));
strippedexprs = lappend(strippedexprs,
processIndirection((Node *) tle->expr,
context));
}
if (action->targetList)
appendStringInfoChar(buf, ')');
if (action->override)
{
if (action->override == OVERRIDING_SYSTEM_VALUE)
appendStringInfoString(buf, " OVERRIDING SYSTEM VALUE");
else if (action->override == OVERRIDING_USER_VALUE)
appendStringInfoString(buf, " OVERRIDING USER VALUE");
}
if (strippedexprs)
{
appendContextKeyword(context, " VALUES (",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 4);
get_rule_list_toplevel(strippedexprs, context, false);
appendStringInfoChar(buf, ')');
}
else
appendStringInfoString(buf, " DEFAULT VALUES");
}
else if (action->commandType == CMD_UPDATE)
{
appendStringInfoString(buf, "UPDATE SET ");
get_update_query_targetlist_def(query, action->targetList,
context, rte);
}
else if (action->commandType == CMD_DELETE)
appendStringInfoString(buf, "DELETE");
else if (action->commandType == CMD_NOTHING)
appendStringInfoString(buf, "DO NOTHING");
}
/* No RETURNING support in MERGE yet */
Assert(query->returningList == NIL);
}
/* ----------
* get_utility_query_def - Parse back a UTILITY parsetree
* ----------

View File

@ -3585,6 +3585,91 @@ MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
DELETE
WHEN NOT MATCHED THEN
INSERT VALUES (s.a, '');
-- test deparsing
CREATE TABLE sf_target(id int, data text, filling int[]);
CREATE FUNCTION merge_sf_test()
RETURNS void
LANGUAGE sql
BEGIN ATOMIC
MERGE INTO sf_target t
USING rule_merge1 s
ON (s.a = t.id)
WHEN MATCHED
AND (s.a + t.id) = 42
THEN UPDATE SET data = repeat(t.data, s.a) || s.b, id = length(s.b)
WHEN NOT MATCHED
AND (s.b IS NOT NULL)
THEN INSERT (data, id)
VALUES (s.b, s.a)
WHEN MATCHED
AND length(s.b || t.data) > 10
THEN UPDATE SET data = s.b
WHEN MATCHED
AND s.a > 200
THEN UPDATE SET filling[s.a] = t.id
WHEN MATCHED
AND s.a > 100
THEN DELETE
WHEN MATCHED
THEN DO NOTHING
WHEN NOT MATCHED
AND s.a > 200
THEN INSERT DEFAULT VALUES
WHEN NOT MATCHED
AND s.a > 100
THEN INSERT (id, data) OVERRIDING USER VALUE
VALUES (s.a, DEFAULT)
WHEN NOT MATCHED
AND s.a > 0
THEN INSERT
VALUES (s.a, s.b, DEFAULT)
WHEN NOT MATCHED
THEN INSERT (filling[1], id)
VALUES (s.a, s.a);
END;
\sf merge_sf_test
CREATE OR REPLACE FUNCTION public.merge_sf_test()
RETURNS void
LANGUAGE sql
BEGIN ATOMIC
MERGE INTO sf_target t
USING rule_merge1 s
ON (s.a = t.id)
WHEN MATCHED
AND ((s.a + t.id) = 42)
THEN UPDATE SET data = (repeat(t.data, s.a) || s.b), id = length(s.b)
WHEN NOT MATCHED
AND (s.b IS NOT NULL)
THEN INSERT (data, id)
VALUES (s.b, s.a)
WHEN MATCHED
AND (length((s.b || t.data)) > 10)
THEN UPDATE SET data = s.b
WHEN MATCHED
AND (s.a > 200)
THEN UPDATE SET filling[s.a] = t.id
WHEN MATCHED
AND (s.a > 100)
THEN DELETE
WHEN MATCHED
THEN DO NOTHING
WHEN NOT MATCHED
AND (s.a > 200)
THEN INSERT DEFAULT VALUES
WHEN NOT MATCHED
AND (s.a > 100)
THEN INSERT (id, data) OVERRIDING USER VALUE
VALUES (s.a, DEFAULT)
WHEN NOT MATCHED
AND (s.a > 0)
THEN INSERT (id, data, filling)
VALUES (s.a, s.b, DEFAULT)
WHEN NOT MATCHED
THEN INSERT (filling[1], id)
VALUES (s.a, s.a);
END
DROP FUNCTION merge_sf_test;
DROP TABLE sf_target;
--
-- Test enabling/disabling
--

View File

@ -1277,6 +1277,55 @@ MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
WHEN NOT MATCHED THEN
INSERT VALUES (s.a, '');
-- test deparsing
CREATE TABLE sf_target(id int, data text, filling int[]);
CREATE FUNCTION merge_sf_test()
RETURNS void
LANGUAGE sql
BEGIN ATOMIC
MERGE INTO sf_target t
USING rule_merge1 s
ON (s.a = t.id)
WHEN MATCHED
AND (s.a + t.id) = 42
THEN UPDATE SET data = repeat(t.data, s.a) || s.b, id = length(s.b)
WHEN NOT MATCHED
AND (s.b IS NOT NULL)
THEN INSERT (data, id)
VALUES (s.b, s.a)
WHEN MATCHED
AND length(s.b || t.data) > 10
THEN UPDATE SET data = s.b
WHEN MATCHED
AND s.a > 200
THEN UPDATE SET filling[s.a] = t.id
WHEN MATCHED
AND s.a > 100
THEN DELETE
WHEN MATCHED
THEN DO NOTHING
WHEN NOT MATCHED
AND s.a > 200
THEN INSERT DEFAULT VALUES
WHEN NOT MATCHED
AND s.a > 100
THEN INSERT (id, data) OVERRIDING USER VALUE
VALUES (s.a, DEFAULT)
WHEN NOT MATCHED
AND s.a > 0
THEN INSERT
VALUES (s.a, s.b, DEFAULT)
WHEN NOT MATCHED
THEN INSERT (filling[1], id)
VALUES (s.a, s.a);
END;
\sf merge_sf_test
DROP FUNCTION merge_sf_test;
DROP TABLE sf_target;
--
-- Test enabling/disabling
--