Copilot commented on code in PR #2038:
URL: https://github.com/apache/cloudberry/pull/2038#discussion_r4064441040


##########
gpcontrib/gp_stats_collector/src/pg_query_state/qs_types.h:
##########
@@ -69,7 +163,8 @@ typedef struct GpscNodeSample
        int32_t plan_node_id;            /* Plan.plan_node_id */
        int32_t parent_plan_node_id;     /* parent's plan_node_id, or
                                                                          * 
GPSC_NO_PARENT_PLAN_NODE_ID at the root */
-       int32_t node_tag;                /* nodeTag(plan) */
+       QsPlanNodeType node_type;        /* qs_map_node_type(nodeTag(plan)); a
+                                                                         * 
protocol value, not a raw NodeTag */

Review Comment:
   If `GpscNodeSample` is part of a binary/shared-memory interface, storing 
`node_type` as a C `enum` can be less robust than a fixed-width integer: enum 
underlying size is compiler/flags dependent (e.g., `-fshort-enums`). To make 
the sample struct layout unambiguous, consider storing this as `int32_t` 
(protocol code) and casting at the edges, or add a compile-time assertion that 
`sizeof(QsPlanNodeType) == sizeof(int32_t)` in the relevant compilation units.



##########
src/backend/commands/explain.c:
##########
@@ -2967,8 +2967,15 @@ ExplainNode(PlanState *planstate, List *ancestors,
        if (es->wal && planstate->instrument)
                show_wal_usage(es, &planstate->instrument->walusage);
 
-       /* Show worker detail after query execution */
-       if (es->analyze && es->verbose && planstate->worker_instrument
+       /*
+        * Prepare per-worker buffer/WAL usage, after query execution.
+        *
+        * es->workers_state is NULL when per-worker detail is hidden (see
+        * es->hide_workers), and ExplainOpenWorker() below requires it, so 
testing
+        * it is what keeps this safe -- planstate->worker_instrument alone is 
not
+        * enough.
+        */
+       if (es->workers_state && (es->buffers || es->wal) && es->verbose
                && !es->runtime)

Review Comment:
   This block no longer checks `planstate->worker_instrument` before 
dereferencing/using it (it’s assigned immediately after the condition). 
Previously the condition included `planstate->worker_instrument` (per the 
removed code), so this change can make EXPLAIN crash if `worker_instrument` is 
NULL while `es->workers_state` is non-NULL. Add `planstate->worker_instrument` 
back into the `if` condition (or otherwise guard the block) so the subsequent 
code can’t run with a NULL worker instrumentation pointer.



##########
src/backend/commands/explain.c:
##########
@@ -4505,16 +4512,27 @@ show_instrumentation_count(const char *qlabel, int 
which,
 
        if (!es->analyze || !planstate->instrument)
                return;
-       nloops = planstate->instrument->nloops;
+
        if (which == 2)
-               nfiltered = ((nloops > 0) ? planstate->instrument->nfiltered2 / 
nloops : 0);
+               nfiltered = planstate->instrument->nfiltered2;
        else
-               nfiltered = ((nloops > 0) ? planstate->instrument->nfiltered1 / 
nloops : 0);
+               nfiltered = planstate->instrument->nfiltered1;
        nloops = planstate->instrument->nloops;
 
-       /* In text mode, suppress zero counts; they're not interesting enough */
+       /*
+        * In text mode, suppress zero counts; they're not interesting enough.
+        *
+        * The nloops == 0 case is what runtime mode hits for the whole of the 
first
+        * loop, so the counters cannot be averaged there; report 0 rather than
+        * dividing by zero.
+        */
        if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)
-               ExplainPropertyFloat(qlabel, NULL, nfiltered, 0, es);
+       {
+               if (nloops > 0)
+                       ExplainPropertyFloat(qlabel, NULL, nfiltered / nloops, 
0, es);
+               else
+                       ExplainPropertyFloat(qlabel, NULL, 0.0, 0, es);
+       }

Review Comment:
   The suppression predicate now uses the raw (non-averaged) `nfiltered`, but 
the printed value is `nfiltered / nloops` (or `0.0` when `nloops == 0`). This 
can emit a 0.0 property in TEXT format when `nfiltered > 0` but `nloops == 0`, 
which contradicts the stated intent to 'suppress zero counts' and is a behavior 
change from the prior code (which would suppress in that case). Compute the 
value-to-report first (respecting the `nloops == 0` case), then base the 
suppression check on the value actually printed.



##########
gpcontrib/gp_stats_collector/src/pg_query_state/signal_handler.c:
##########
@@ -222,13 +222,110 @@ send_msg_by_parts(shm_mq_handle *mqh, Size nbytes, const 
void *data)
        return MSG_BY_PARTS_SUCCEEDED;
 }
 
+/*
+ * qs_map_node_type -- translate a NodeTag into the wire protocol's stable
+ * QsPlanNodeType.
+ *
+ * NodeTag numbering is a PostgreSQL implementation detail and is not stable
+ * across major versions: PG16 generates it with gen_node_support.pl
+ * (src/include/nodes/nodetags.h), which renumbered every tag relative to PG14
+ * (T_SeqScan 27 -> 395).  Sending nodeTag(plan) raw therefore makes every node
+ * unresolvable on a receiver holding a table for the other version.  Map
+ * explicitly so the protocol is decoupled from the backend's numbering.
+ *
+ * The cases below mirror the node-name switch in ExplainNode(); anything not
+ * listed is reported as UNSPECIFIED rather than guessed.  QS_PLAN_NODE_TYPE_
+ * SPLIT_MERGE has no PostgreSQL 14 counterpart, so it is reserved in the enum
+ * but has no case here.
+ */
+static QsPlanNodeType
+qs_map_node_type(NodeTag tag)
+{
+       switch (tag)
+       {
+               /* control nodes */
+               case T_Result:                                  return 
QS_PLAN_NODE_TYPE_RESULT;
+               case T_ProjectSet:                              return 
QS_PLAN_NODE_TYPE_PROJECT_SET;
+               case T_ModifyTable:                             return 
QS_PLAN_NODE_TYPE_MODIFY_TABLE;
+               case T_Append:                                  return 
QS_PLAN_NODE_TYPE_APPEND;
+               case T_MergeAppend:                             return 
QS_PLAN_NODE_TYPE_MERGE_APPEND;
+               case T_RecursiveUnion:                  return 
QS_PLAN_NODE_TYPE_RECURSIVE_UNION;
+               case T_BitmapAnd:                               return 
QS_PLAN_NODE_TYPE_BITMAP_AND;
+               case T_BitmapOr:                                return 
QS_PLAN_NODE_TYPE_BITMAP_OR;
+
+               /* scans */
+               case T_SeqScan:                                 return 
QS_PLAN_NODE_TYPE_SEQ_SCAN;
+               case T_SampleScan:                              return 
QS_PLAN_NODE_TYPE_SAMPLE_SCAN;
+               case T_IndexScan:                               return 
QS_PLAN_NODE_TYPE_INDEX_SCAN;
+               case T_IndexOnlyScan:                   return 
QS_PLAN_NODE_TYPE_INDEX_ONLY_SCAN;
+               case T_BitmapIndexScan:                 return 
QS_PLAN_NODE_TYPE_BITMAP_INDEX_SCAN;
+               case T_BitmapHeapScan:                  return 
QS_PLAN_NODE_TYPE_BITMAP_HEAP_SCAN;
+               case T_TidScan:                                 return 
QS_PLAN_NODE_TYPE_TID_SCAN;
+               case T_TidRangeScan:                    return 
QS_PLAN_NODE_TYPE_TID_RANGE_SCAN;
+               case T_SubqueryScan:                    return 
QS_PLAN_NODE_TYPE_SUBQUERY_SCAN;
+               case T_FunctionScan:                    return 
QS_PLAN_NODE_TYPE_FUNCTION_SCAN;
+               case T_TableFuncScan:                   return 
QS_PLAN_NODE_TYPE_TABLE_FUNC_SCAN;
+               case T_ValuesScan:                              return 
QS_PLAN_NODE_TYPE_VALUES_SCAN;
+               case T_CteScan:                                 return 
QS_PLAN_NODE_TYPE_CTE_SCAN;
+               case T_NamedTuplestoreScan:             return 
QS_PLAN_NODE_TYPE_NAMED_TUPLESTORE_SCAN;
+               case T_WorkTableScan:                   return 
QS_PLAN_NODE_TYPE_WORK_TABLE_SCAN;
+               case T_ForeignScan:                             return 
QS_PLAN_NODE_TYPE_FOREIGN_SCAN;
+               case T_CustomScan:                              return 
QS_PLAN_NODE_TYPE_CUSTOM_SCAN;
+
+               /* joins */
+               case T_NestLoop:                                return 
QS_PLAN_NODE_TYPE_NEST_LOOP;
+               case T_MergeJoin:                               return 
QS_PLAN_NODE_TYPE_MERGE_JOIN;
+               case T_HashJoin:                                return 
QS_PLAN_NODE_TYPE_HASH_JOIN;
+
+               /* materialization, ordering, grouping */
+               case T_Material:                                return 
QS_PLAN_NODE_TYPE_MATERIAL;
+               case T_Memoize:                                 return 
QS_PLAN_NODE_TYPE_MEMOIZE;
+               case T_Sort:                                    return 
QS_PLAN_NODE_TYPE_SORT;
+               case T_IncrementalSort:                 return 
QS_PLAN_NODE_TYPE_INCREMENTAL_SORT;
+               case T_Group:                                   return 
QS_PLAN_NODE_TYPE_GROUP;
+               case T_Agg:                                             return 
QS_PLAN_NODE_TYPE_AGG;
+               case T_WindowAgg:                               return 
QS_PLAN_NODE_TYPE_WINDOW_AGG;
+               case T_Unique:                                  return 
QS_PLAN_NODE_TYPE_UNIQUE;
+               case T_Hash:                                    return 
QS_PLAN_NODE_TYPE_HASH;
+               case T_SetOp:                                   return 
QS_PLAN_NODE_TYPE_SET_OP;
+               case T_LockRows:                                return 
QS_PLAN_NODE_TYPE_LOCK_ROWS;
+               case T_Limit:                                   return 
QS_PLAN_NODE_TYPE_LIMIT;
+
+               /* intra-node parallelism */
+               case T_Gather:                                  return 
QS_PLAN_NODE_TYPE_GATHER;
+               case T_GatherMerge:                             return 
QS_PLAN_NODE_TYPE_GATHER_MERGE;
+
+               /* Cloudberry MPP nodes */
+               case T_Motion:                                  return 
QS_PLAN_NODE_TYPE_MOTION;
+               case T_Sequence:                                return 
QS_PLAN_NODE_TYPE_SEQUENCE;
+               case T_ShareInputScan:                  return 
QS_PLAN_NODE_TYPE_SHARE_INPUT_SCAN;
+               case T_SplitUpdate:                             return 
QS_PLAN_NODE_TYPE_SPLIT_UPDATE;
+               case T_AssertOp:                                return 
QS_PLAN_NODE_TYPE_ASSERT_OP;
+               case T_PartitionSelector:               return 
QS_PLAN_NODE_TYPE_PARTITION_SELECTOR;
+               case T_RuntimeFilter:                   return 
QS_PLAN_NODE_TYPE_RUNTIME_FILTER;
+               case T_TupleSplit:                              return 
QS_PLAN_NODE_TYPE_TUPLE_SPLIT;
+               case T_TableFunctionScan:               return 
QS_PLAN_NODE_TYPE_TABLE_FUNCTION_SCAN;
+               case T_DynamicSeqScan:                  return 
QS_PLAN_NODE_TYPE_DYNAMIC_SEQ_SCAN;
+               case T_DynamicIndexScan:                return 
QS_PLAN_NODE_TYPE_DYNAMIC_INDEX_SCAN;
+               case T_DynamicIndexOnlyScan:    return 
QS_PLAN_NODE_TYPE_DYNAMIC_INDEX_ONLY_SCAN;
+               case T_DynamicBitmapIndexScan:  return 
QS_PLAN_NODE_TYPE_DYNAMIC_BITMAP_INDEX_SCAN;
+               case T_DynamicBitmapHeapScan:   return 
QS_PLAN_NODE_TYPE_DYNAMIC_BITMAP_HEAP_SCAN;
+               case T_DynamicForeignScan:              return 
QS_PLAN_NODE_TYPE_DYNAMIC_FOREIGN_SCAN;
+
+               default:
+                       elog(DEBUG1, "pg_query_state: unmapped plan NodeTag 
%d", (int) tag);
+                       return QS_PLAN_NODE_TYPE_UNSPECIFIED;

Review Comment:
   Logging per unmapped node tag can spam logs in environments where DEBUG1 is 
enabled, especially since mapping runs per plan node during sampling. Consider 
rate-limiting (e.g., log once per `NodeTag` value per process) or removing the 
log entirely and relying on `UNSPECIFIED` as a silent fallback.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to