Add OID descriptions to dumped parse/query/plan trees

Started by Chao Li5 months ago4 messages
#1Chao Li
li.evan.chao@gmail.com
1 attachment(s)

Hi Hackers,

When reading dumped parse/query/plan trees, sometime we want to know
what exact type/operator/function an OID stands for. I just added a new
feature that will add some OID descriptions to the dump trees, like:

```

      :scan.scanrelid 1
      :indexid 16474 # index orders_pkey
      :indexqual (
         {OPEXPR
         :opno 521 # operator >(integer,integer)
         :opfuncid 147 # function int4gt(integer,integer)
         :opresulttype 16 # type boolean
         :opretset false
         :opcollid 0

```

The patch file is attached.

Best regards,

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Attachments:

v1-0001-Add-OID-descriptions-to-dumped-parse-query-plan-t.patchtext/plain; charset=UTF-8; name=v1-0001-Add-OID-descriptions-to-dumped-parse-query-plan-t.patchDownload
From 182a6020b7b9f5733c773f0f04e472b59c223144 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
Date: Fri, 22 Aug 2025 13:55:13 +0800
Subject: [PATCH v1] Add OID descriptions to dumped parse/query/plan trees

Include descriptions for certain OIDs in dumped trees, using "#" in the
style of code comments. This makes the trees easier to read. For example:

```
  :scan.scanrelid 1
  :indexid 16474 # index orders_pkey
  :indexqual (
     {OPEXPR
     :opno 521 # operator >(integer,integer)
     :opfuncid 147 # function int4gt(integer,integer)
     :opresulttype 16 # type boolean
     :opretset false
     :opcollid 0
```

This patch also fixes a trivial bug where an extra whitespace was added
when dumping an array, for example:

```
  :sort.numCols 2
  :sort.sortColIdx ( 1 4)
  :sort.sortOperators ( 97 1754)
  :sort.collations ( 0 0)
  :sort.nullsFirst ( false false)
```

The unnecessary whitespace is now removed.

Author: Li Chao <lic@highgo.com>
---
 src/backend/nodes/outfuncs.c | 99 +++++++++++++++++++++++++++++++++---
 1 file changed, 91 insertions(+), 8 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index eaf391fc2ab..7c0314e7e4a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -17,6 +17,11 @@
 #include <ctype.h>
 
 #include "access/attnum.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
 #include "common/shortest_dec.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
@@ -30,6 +35,8 @@ static bool write_location_fields = false;
 
 static void outChar(StringInfo str, char c);
 static void outDouble(StringInfo str, double d);
+static char *oidToString(const char *attName, Oid oid);
+static char *oidArrayToString(const char *attName, const Oid *oids, int len);
 
 
 /*
@@ -64,7 +71,12 @@ static void outDouble(StringInfo str, double d);
 
 /* Write an OID field (don't hard-wire assumption that OID is same as uint) */
 #define WRITE_OID_FIELD(fldname) \
-	appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname)
+	{ \
+		char * s = oidToString(CppAsString(fldname), node->fldname); \
+		appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname); \
+		if (s != NULL) \
+			appendStringInfo(str, " # %s", s); \
+	}
 
 /* Write a long-integer field */
 #define WRITE_LONG_FIELD(fldname) \
@@ -121,8 +133,12 @@ static void outDouble(StringInfo str, double d);
 
 /* Write a variable-length array of Oid */
 #define WRITE_OID_ARRAY(fldname, len) \
-	(appendStringInfoString(str, " :" CppAsString(fldname) " "), \
-	 writeOidCols(str, node->fldname, len))
+	{ \
+		char *s = oidArrayToString(CppAsString(fldname), node->fldname, len); \
+		appendStringInfoString(str, " :" CppAsString(fldname) " "); \
+	 	writeOidCols(str, node->fldname, len); \
+		if (s != NULL) appendStringInfo(str, " # %s", s); \
+	}
 
 /* Write a variable-length array of Index */
 #define WRITE_INDEX_ARRAY(fldname, len) \
@@ -222,6 +238,69 @@ outDouble(StringInfo str, double d)
 	appendStringInfoString(str, buf);
 }
 
+static char *
+oidToString(const char *attName, Oid oid)
+{
+	ObjectAddress ob;
+
+	ob.objectId = oid;
+	ob.objectSubId = 0;
+	if ((strncmp(attName, "opno", 5) == 0) ||
+		(strncmp(attName, "eqop", 5) == 0) ||
+		(strncmp(attName, "sortop", 7) == 0) ||
+		(strncmp(attName, "sort.sortOperators", 19) == 0))
+	{
+		ob.classId = OperatorRelationId;
+	}
+	else if ((strncmp(attName, "opfuncid", 9) == 0) ||
+			 (strncmp(attName, "funcid", 7) == 0))
+	{
+		ob.classId = ProcedureRelationId;
+	}
+	else if ((strncmp(attName, "opresulttype", 13) == 0) ||
+			 (strncmp(attName, "vartype", 8) == 0) ||
+			 (strncmp(attName, "consttype", 10) == 0) ||
+			 (strncmp(attName, "funcresulttype", 15) == 0))
+	{
+		ob.classId = TypeRelationId;
+	}
+	else if ((strncmp(attName, "relid", 5) == 0) ||
+			 (strncmp(attName, "indexid", 8) == 0))
+	{
+		ob.classId = RelationRelationId;
+	}
+	else
+	{
+		return NULL;
+	}
+	return getObjectDescription(&ob, true);
+}
+
+static char *
+oidArrayToString(const char *attName, const Oid *oids, int len)
+{
+	StringInfoData str;
+	int			i;
+	char	   *desc;
+
+	initStringInfo(&str);
+	for (i = 0; i < len; i++)
+	{
+		desc = oidToString(attName, oids[i]);
+		if (desc != NULL)
+		{
+			if (i > 0)
+				appendStringInfoString(&str, ", ");
+			appendStringInfo(&str, "%s", desc);
+		}
+		else
+		{
+			return NULL;
+		}
+	}
+	return str.data;
+}
+
 /*
  * common implementation for scalar-array-writing functions
  *
@@ -238,18 +317,22 @@ fnname(StringInfo str, const datatype *arr, int len) \
 	{ \
 		appendStringInfoChar(str, '('); \
 		for (int i = 0; i < len; i++) \
+		{ \
 			appendStringInfo(str, fmtstr, convfunc(arr[i])); \
+			if (i < len - 1) \
+				appendStringInfoChar(str, ' '); \
+		} \
 		appendStringInfoChar(str, ')'); \
 	} \
 	else \
 		appendStringInfoString(str, "<>"); \
 }
 
-WRITE_SCALAR_ARRAY(writeAttrNumberCols, AttrNumber, " %d",)
-WRITE_SCALAR_ARRAY(writeOidCols, Oid, " %u",)
-WRITE_SCALAR_ARRAY(writeIndexCols, Index, " %u",)
-WRITE_SCALAR_ARRAY(writeIntCols, int, " %d",)
-WRITE_SCALAR_ARRAY(writeBoolCols, bool, " %s", booltostr)
+WRITE_SCALAR_ARRAY(writeAttrNumberCols, AttrNumber, "%d",)
+WRITE_SCALAR_ARRAY(writeOidCols, Oid, "%u",)
+WRITE_SCALAR_ARRAY(writeIndexCols, Index, "%u",)
+WRITE_SCALAR_ARRAY(writeIntCols, int, "%d",)
+WRITE_SCALAR_ARRAY(writeBoolCols, bool, "%s", booltostr)
 
 /*
  * Print an array (not a List) of Node pointers.
-- 
2.39.5 (Apple Git-154)

#2Kirill Reshke
reshkekirill@gmail.com
In reply to: Chao Li (#1)
Re: Add OID descriptions to dumped parse/query/plan trees

On Fri, 22 Aug 2025 at 14:08, Chao Li <li.evan.chao@gmail.com> wrote:

Hi Hackers,

When reading dumped parse/query/plan trees, sometime we want to know what exact type/operator/function an OID stands for. I just added a new feature that will add some OID descriptions to the dump trees, like:

```

:scan.scanrelid 1
:indexid 16474 # index orders_pkey
:indexqual (
{OPEXPR
:opno 521 # operator >(integer,integer)
:opfuncid 147 # function int4gt(integer,integer)
:opresulttype 16 # type boolean
:opretset false
:opcollid 0

```

The patch file is attached.

Best regards,

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

Hi!

In commit message:

"This patch also fixes a trivial bug where an extra whitespace was added
when dumping an array, for example:..."

Maybe worth making this a separate change?

I can see that plain text comments can be beneficial for debug
purposes. However, I am not terribly sure these comments should be
included in the text dump unconditionally. This can break some
third-party query analysing tools (as it changes format). Can we make
this optional?

--
Best regards,
Kirill Reshke

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Chao Li (#1)
Re: Add OID descriptions to dumped parse/query/plan trees

Chao Li <li.evan.chao@gmail.com> writes:

When reading dumped parse/query/plan trees, sometime we want to know
what exact type/operator/function an OID stands for. I just added a new
feature that will add some OID descriptions to the dump trees, like:

This is really quite a bad idea. It means that the dump code has to
execute in a live (not-aborted) transaction, which is not a
requirement it had before, and will complicate some debugging
scenarios. The extra catalog accesses that could occur might also
cause problems, maybe even deadlocks in some uses. I'd prefer to
keep the flexibility to be able to call pprint from pretty much
anywhere.

Also, AFAICS this breaks re-reading of dump trees, which breaks all
kinds of stuff (rules, views, parallel query, etc).

regards, tom lane

#4Chao Li
li.evan.chao@gmail.com
In reply to: Tom Lane (#3)
Re: Add OID descriptions to dumped parse/query/plan trees

On Aug 22, 2025, at 22:10, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Chao Li <li.evan.chao@gmail.com> writes:

When reading dumped parse/query/plan trees, sometime we want to know
what exact type/operator/function an OID stands for. I just added a new
feature that will add some OID descriptions to the dump trees, like:

The extra catalog accesses that could occur might also
cause problems, maybe even deadlocks in some uses. I'd prefer to
keep the flexibility to be able to call pprint from pretty much
anywhere.

Also, AFAICS this breaks re-reading of dump trees, which breaks all
kinds of stuff (rules, views, parallel query, etc).

regards, tom lane

Thanks for pointing out that, I withdraw the patch.

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/