From ebcfc761907c72f40a856f3552c814ee1d0f1f0f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:08:29 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20Column=20Nam?= =?UTF-8?q?e=20Resolution=20from=20Edge=20Handles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the column name directly from the handle by parsing the hexadecimal unicode points. Instead of re-encoding every column to match the handle, parse the handle once and verify the existence of the parsed column name within the node's columns using a direct O(1) comparison (in a simple loop). Ensure you validate the column still exists to avoid regressing cases where dangling edges reference deleted columns. --- .jules/bolt.md | 3 +++ frontend/src/erd/export.ts | 32 +++++++++++++++++++++++++++++++- frontend/src/erd/handleUtils.ts | 12 ++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index f1a8c1466..47b85579f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -77,3 +77,6 @@ Optimized metric route processing to O(N) by creating a mapping of routes direct ## 2024-07-13 - [Optimize Export Dictionary FK lookups] **Learning:** Found O(N * C * E) performance bottleneck in ERD export dictionaries due to repeated array searching with `edges.some()` inside a nested loop over nodes and columns. **Action:** Replace repeated linear array scans for edges by precomputing O(1) Set lookups of foreign key column handles per node before looping. +## 2025-02-28 - Optimize Column Name Resolution from Edge Handles +**Learning:** Resolving column names from edge handles (which are in the format src-c-XXXX-XXXX) by iterating over all columns and re-encoding their names to compare with the handle creates an O(N) performance bottleneck per edge. This approach leads to repeated string encoding and allocation. +**Action:** Extract the column name directly from the handle by parsing the hexadecimal unicode points. Instead of re-encoding every column to match the handle, parse the handle once and verify the existence of the parsed column name within the node's columns using a direct O(1) comparison (in a simple loop). Ensure you validate the column still exists to avoid regressing cases where dangling edges reference deleted columns. diff --git a/frontend/src/erd/export.ts b/frontend/src/erd/export.ts index 62ce7219e..6a13d6c76 100644 --- a/frontend/src/erd/export.ts +++ b/frontend/src/erd/export.ts @@ -2,7 +2,7 @@ import type { Node, Edge } from '@xyflow/react'; import { normalizeBusinessGroupColor } from './businessGroups'; import type { IndexRecommendation } from './cardinality'; import type { ForeignKeyEdgeData, TableNodeData } from './convert'; -import { sourceColumnHandleId, targetColumnHandleId } from './handleUtils'; +import { parseColumnNameFromHandle, sourceColumnHandleId, targetColumnHandleId } from './handleUtils'; export * from './exportDataDictionary'; @@ -67,6 +67,36 @@ function fkColumnsForEdge( return { sourceColumns, targetColumns }; } + const parsedSource = edge.sourceHandle ? parseColumnNameFromHandle(edge.sourceHandle) : null; + const parsedTarget = edge.targetHandle ? parseColumnNameFromHandle(edge.targetHandle) : null; + + if (parsedSource !== null && parsedTarget !== null) { + // Validate that the parsed column actually exists in the node data + const sourceColumnsArr = sourceNode.data.columns || []; + const targetColumnsArr = targetNode.data.columns || []; + + // Using simple loop/some instead of map+includes for better performance + let sourceExists = false; + for (let i = 0; i < sourceColumnsArr.length; i++) { + if (sourceColumnsArr[i] && sourceColumnsArr[i]!.column_name === parsedSource) { + sourceExists = true; + break; + } + } + + let targetExists = false; + for (let i = 0; i < targetColumnsArr.length; i++) { + if (targetColumnsArr[i] && targetColumnsArr[i]!.column_name === parsedTarget) { + targetExists = true; + break; + } + } + + if (sourceExists && targetExists) { + return { sourceColumns: [parsedSource], targetColumns: [parsedTarget] }; + } + } + const sourceHandleColumn = (sourceNode.data.columns || []) .find((column) => sourceColumnHandleId(column.column_name) === edge.sourceHandle) ?.column_name; diff --git a/frontend/src/erd/handleUtils.ts b/frontend/src/erd/handleUtils.ts index 054d5ab2a..1650ef092 100644 --- a/frontend/src/erd/handleUtils.ts +++ b/frontend/src/erd/handleUtils.ts @@ -14,3 +14,15 @@ export function sourceColumnHandleId(columnName: string): string { export function targetColumnHandleId(columnName: string): string { return `tgt-${sanitizeHandleId(columnName)}` } + +export function parseColumnNameFromHandle(handleId: string): string | null { + const prefixMatch = handleId.match(/^(?:src-|tgt-)?c-(.+)$/); + if (!prefixMatch) return null; + const encoded = prefixMatch[1]!; + if (encoded === 'empty') return ''; + try { + return encoded.split('-').map(code => String.fromCodePoint(parseInt(code, 16))).join(''); + } catch { + return null; + } +} From 4071df164dcd8c4b0accc9b9abf2931170619d77 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:42:50 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20Column=20Nam?= =?UTF-8?q?e=20Resolution=20from=20Edge=20Handles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the column name directly from the handle by parsing the hexadecimal unicode points. Instead of re-encoding every column to match the handle, parse the handle once and verify the existence of the parsed column name within the node's columns using a direct O(1) comparison via `.some()`. Ensure you validate the column still exists to avoid regressing cases where dangling edges reference deleted columns. --- frontend/src/erd/export.ts | 19 +++---------------- frontend/src/erd/handleUtils.ts | 4 ++-- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/frontend/src/erd/export.ts b/frontend/src/erd/export.ts index 6a13d6c76..89b4e923f 100644 --- a/frontend/src/erd/export.ts +++ b/frontend/src/erd/export.ts @@ -75,22 +75,9 @@ function fkColumnsForEdge( const sourceColumnsArr = sourceNode.data.columns || []; const targetColumnsArr = targetNode.data.columns || []; - // Using simple loop/some instead of map+includes for better performance - let sourceExists = false; - for (let i = 0; i < sourceColumnsArr.length; i++) { - if (sourceColumnsArr[i] && sourceColumnsArr[i]!.column_name === parsedSource) { - sourceExists = true; - break; - } - } - - let targetExists = false; - for (let i = 0; i < targetColumnsArr.length; i++) { - if (targetColumnsArr[i] && targetColumnsArr[i]!.column_name === parsedTarget) { - targetExists = true; - break; - } - } + // Check existence using .some() + const sourceExists = sourceColumnsArr.some((c) => c && c.column_name === parsedSource); + const targetExists = targetColumnsArr.some((c) => c && c.column_name === parsedTarget); if (sourceExists && targetExists) { return { sourceColumns: [parsedSource], targetColumns: [parsedTarget] }; diff --git a/frontend/src/erd/handleUtils.ts b/frontend/src/erd/handleUtils.ts index 1650ef092..1b07342e7 100644 --- a/frontend/src/erd/handleUtils.ts +++ b/frontend/src/erd/handleUtils.ts @@ -17,8 +17,8 @@ export function targetColumnHandleId(columnName: string): string { export function parseColumnNameFromHandle(handleId: string): string | null { const prefixMatch = handleId.match(/^(?:src-|tgt-)?c-(.+)$/); - if (!prefixMatch) return null; - const encoded = prefixMatch[1]!; + if (!prefixMatch || !prefixMatch[1]) return null; + const encoded = prefixMatch[1]; if (encoded === 'empty') return ''; try { return encoded.split('-').map(code => String.fromCodePoint(parseInt(code, 16))).join(''); From 8988c155c5b8c8b3390fb1404206c90859be1588 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:39:15 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20Column=20Nam?= =?UTF-8?q?e=20Resolution=20from=20Edge=20Handles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the column name directly from the handle by parsing the hexadecimal unicode points. Instead of re-encoding every column to match the handle, parse the handle once and verify the existence of the parsed column name within the node's columns using a direct O(1) comparison via `.some()`. Ensure you validate the column still exists to avoid regressing cases where dangling edges reference deleted columns. From cdd5ab41d2ba1841def99ca22030c254adc46d69 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:17:40 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20Column=20Nam?= =?UTF-8?q?e=20Resolution=20from=20Edge=20Handles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the column name directly from the handle by parsing the hexadecimal unicode points. Instead of re-encoding every column to match the handle, parse the handle once and verify the existence of the parsed column name within the node's columns using a direct O(1) comparison via `.some()`. Ensure you validate the column still exists to avoid regressing cases where dangling edges reference deleted columns. From 2eb62ac0cfa2ef71413d99ab171c6014d3833a5f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:44:39 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20Column=20Nam?= =?UTF-8?q?e=20Resolution=20from=20Edge=20Handles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the column name directly from the handle by parsing the hexadecimal unicode points. Instead of re-encoding every column to match the handle, parse the handle once and verify the existence of the parsed column name within the node's columns using a direct O(1) comparison via `.some()`. Ensure you validate the column still exists to avoid regressing cases where dangling edges reference deleted columns.