Repository: dalibo/pev2
Pull request #738 (2025-04-10 08:41:41)

Merge nodes relation name parsing


Patch:
diff --git a/src/enums.ts b/src/enums.ts
index a20e797..3ac4d70 100644
--- a/src/enums.ts
+++ b/src/enums.ts
@@ -98,6 +98,7 @@ export enum NodeProp {
   FILTER = "Filter",
   STRATEGY = "Strategy",
   PARTIAL_MODE = "Partial Mode",
+  OPERATION = "Operation",
 
   // computed by pev
   NODE_ID = "nodeId",
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 2b3336c..5330c20 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -133,8 +133,8 @@ export class Node {
     }
     this[NodeProp.NODE_TYPE] = type
     // tslint:disable-next-line:max-line-length
-    const scanMatches =
-      /^((?:Parallel\s+)?(?:Seq\sScan|Tid.*Scan|Bitmap\s+Heap\s+Scan|(?:Async\s+)?Foreign\s+Scan|Update|Insert|Delete))\son\s(\S+)(?:\s+(\S+))?$/.exec(
+    const scanAndOperationMatches =
+      /^((?:Parallel\s+)?(?:Seq\sScan|Tid.*Scan|Bitmap\s+Heap\s+Scan|(?:Async\s+)?Foreign\s+Scan|Update|Insert|Delete|Merge))\son\s(\S+)(?:\s+(\S+))?$/.exec(
         type
       )
     const bitmapMatches = /^(Bitmap\s+Index\s+Scan)\son\s(\S+)$/.exec(type)
@@ -148,11 +148,11 @@ export class Node {
       type
     )
     const subqueryMatches = /^(Subquery\sScan)\son\s(.+)$/.exec(type)
-    if (scanMatches) {
-      this[NodeProp.NODE_TYPE] = scanMatches[1]
-      this[NodeProp.RELATION_NAME] = scanMatches[2]
-      if (scanMatches[3]) {
-        this[NodeProp.ALIAS] = scanMatches[3]
+    if (scanAndOperationMatches) {
+      this[NodeProp.NODE_TYPE] = scanAndOperationMatches[1]
+      this[NodeProp.RELATION_NAME] = scanAndOperationMatches[2]
+      if (scanAndOperationMatches[3]) {
+        this[NodeProp.ALIAS] = scanAndOperationMatches[3]
       }
     } else if (bitmapMatches) {
       this[NodeProp.NODE_TYPE] = bitmapMatches[1]
diff --git a/src/services/plan-service.ts b/src/services/plan-service.ts
index 497073e..bd734f3 100644
--- a/src/services/plan-service.ts
+++ b/src/services/plan-service.ts
@@ -1275,5 +1275,9 @@ export class PlanService {
       }
       node[NodeProp.NODE_TYPE] = prefix + "Aggregate"
     }
+
+    if (node[NodeProp.NODE_TYPE] == "ModifyTable") {
+      node[NodeProp.NODE_TYPE] = node[NodeProp.OPERATION] as string
+    }
   }
 }


Test patch:
diff --git a/src/services/__tests__/20-triggers-info.spec.ts b/src/services/__tests__/20-triggers-info.spec.ts
new file mode 100644
index 0000000..f3c06e3
--- /dev/null
+++ b/src/services/__tests__/20-triggers-info.spec.ts
@@ -0,0 +1,81 @@
+import { describe, expect, test } from "vitest"
+import { PlanService } from "@/services/plan-service"
+import type { IPlan, IPlanContent } from "@/interfaces"
+
+describe("PlanService", () => {
+  test("Insert node type is correct from text", () => {
+    const planService = new PlanService()
+    const source = `
+      Insert on foo  (cost=0.00..0.01 rows=1 width=24) (actual time=0.504..0.505 rows=0 loops=1)
+        ->  Result  (cost=0.00..0.01 rows=1 width=24) (actual time=0.019..0.020 rows=1 loops=1)
+      Planning Time: 0.072 ms
+      Trigger foo_system_time_generated_always: time=0.401 calls=1
+      Trigger foo_system_time_write_history: time=0.260 calls=1
+      Execution Time: 0.842 ms
+    `
+    const r = planService.fromSource(source) as IPlanContent
+    const plan: IPlan = planService.createPlan("", r, "")
+    const root = plan.content.Plan
+    expect(root["Node Type"]).toEqual("Insert")
+  })
+
+  test("Insert node type is correct from text", () => {
+    const planService = new PlanService()
+    const source = `
+      [
+        {
+          "Plan": {
+            "Node Type": "ModifyTable",
+            "Operation": "Insert",
+            "Parallel Aware": false,
+            "Relation Name": "foo",
+            "Alias": "foo",
+            "Startup Cost": 0.00,
+            "Total Cost": 0.01,
+            "Plan Rows": 1,
+            "Plan Width": 24,
+            "Actual Startup Time": 0.568,
+            "Actual Total Time": 0.568,
+            "Actual Rows": 0,
+            "Actual Loops": 1,
+            "Plans": [
+              {
+                "Node Type": "Result",
+                "Parent Relationship": "Member",
+                "Parallel Aware": false,
+                "Startup Cost": 0.00,
+                "Total Cost": 0.01,
+                "Plan Rows": 1,
+                "Plan Width": 24,
+                "Actual Startup Time": 0.018,
+                "Actual Total Time": 0.018,
+                "Actual Rows": 1,
+                "Actual Loops": 1
+              }
+            ]
+          },
+          "Planning Time": 0.056,
+          "Triggers": [
+            {
+              "Trigger Name": "foo_system_time_generated_always",
+              "Relation": "foo",
+              "Time": 0.435,
+              "Calls": 1
+            },
+            {
+              "Trigger Name": "foo_system_time_write_history",
+              "Relation": "foo",
+              "Time": 0.207,
+              "Calls": 1
+            }
+          ],
+          "Execution Time": 0.824
+        }
+      ]
+    `
+    const r = planService.fromSource(source) as IPlanContent
+    const plan: IPlan = planService.createPlan("", r, "")
+    const root = plan.content.Plan
+    expect(root["Node Type"]).toEqual("Insert")
+  })
+})
diff --git a/src/services/__tests__/from-text.spec.ts b/src/services/__tests__/from-text.spec.ts
index 1896588..17d0b17 100644
--- a/src/services/__tests__/from-text.spec.ts
+++ b/src/services/__tests__/from-text.spec.ts
@@ -14,7 +14,7 @@ const __filename = fileURLToPath(import.meta.url)
 
 const dir = path.join(path.dirname(__filename), "from-text")
 const files = fs.readdirSync(dir)
-let tests = files.map((file: string) => file.replace(/-.*/, ""))
+let tests = files.map((file: string) => file.replace(/-([^-]+)$/, ""))
 tests = _.uniq(tests)
 tests.forEach((planTest: string) => {
   describe("From text: Plan " + planTest, () => {
diff --git a/src/services/__tests__/from-text/40-merge-nodetype-expect b/src/services/__tests__/from-text/40-merge-nodetype-expect
new file mode 100644
index 0000000..b0ebedb
--- /dev/null
+++ b/src/services/__tests__/from-text/40-merge-nodetype-expect
@@ -0,0 +1,5 @@
+{
+  "Plan": {
+    "Node Type": "Merge"
+  }
+}
diff --git a/src/services/__tests__/from-text/40-merge-nodetype-plan b/src/services/__tests__/from-text/40-merge-nodetype-plan
new file mode 100644
index 0000000..3227ec6
--- /dev/null
+++ b/src/services/__tests__/from-text/40-merge-nodetype-plan
@@ -0,0 +1,1 @@
+Merge on public.tb  (cost=1.00..1.05 rows=0 width=0)