Add ruleutils support for decompiling MERGE commands.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 7 May 2023 15:01:15 +0000 (11:01 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 7 May 2023 15:01:15 +0000 (11:01 -0400)
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

src/backend/utils/adt/ruleutils.c
src/test/regress/expected/rules.out
src/test/regress/sql/rules.sql

index b46cb7c9449995b264b997a09f03608db46dadf1..6b01fef96aadb6c5c2c5de6f04bc83cb59ba5d39 100644 (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);
@@ -5488,6 +5490,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;
@@ -7083,6 +7089,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
  * ----------
index be23f6fe8b40af893b1853888e74eeecf401b652..225204c3a46ef86c505342e5d8814bc0277a443d 100644 (file)
@@ -3571,6 +3571,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
 --
index a96a78a3025177e17283a3122584a076b14dc415..b7ac6407b0dde8381f1e8337a8a6c5047801c605 100644 (file)
@@ -1296,6 +1296,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
 --