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..89b4e923f 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,23 @@ 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 || []; + + // 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] }; + } + } + 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..1b07342e7 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 || !prefixMatch[1]) 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; + } +}