From b9fb298cae45bcbb9c7dc2681d2c14db6630039e Mon Sep 17 00:00:00 2001 From: Bonobo Date: Wed, 2 Sep 2026 13:22:27 +0200 Subject: [PATCH] perf(neo4j): precompute row sort keys --- src/build/neo4j/rows.ts | 19 +++++++++++-------- test/neo4j-edge-identity.test.ts | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/build/neo4j/rows.ts b/src/build/neo4j/rows.ts index 35d2107..0056703 100644 --- a/src/build/neo4j/rows.ts +++ b/src/build/neo4j/rows.ts @@ -109,14 +109,17 @@ export class RowBuilder { finish(): GraphRows { for (const e of this.deferred) if (this.keys.has(e.to.value)) this.edges.push(e); - const nodes = [...this.nodes.values()].sort((a, b) => - `${a.labels[0]}\0${a.value}`.localeCompare(`${b.labels[0]}\0${b.value}`), - ); - const edges = this.edges.sort((a, b) => - `${a.type}\0${a.from.value}\0${a.to.value}`.localeCompare( - `${b.type}\0${b.from.value}\0${b.to.value}`, - ), - ); + const nodes = [...this.nodes.values()] + .map((row) => ({ row, sortKey: `${row.labels[0]}\0${row.value}` })) + .sort((a, b) => a.sortKey.localeCompare(b.sortKey)) + .map(({ row }) => row); + const edges = this.edges + .map((row) => ({ + row, + sortKey: `${row.type}\0${row.from.value}\0${row.to.value}`, + })) + .sort((a, b) => a.sortKey.localeCompare(b.sortKey)) + .map(({ row }) => row); return { nodes, edges }; } } diff --git a/test/neo4j-edge-identity.test.ts b/test/neo4j-edge-identity.test.ts index 0909d31..81d9491 100644 --- a/test/neo4j-edge-identity.test.ts +++ b/test/neo4j-edge-identity.test.ts @@ -43,3 +43,21 @@ describe("edge identity discriminant (_k)", () => { expect(ddg.length).toBe(2); }); }); + +test("row sorting preserves composite-key collation across prefix values", () => { + const builder = new RowBuilder(); + const target = builder.node(["CanNode"], "id", "target", {}); + const file = builder.node(["CanNode"], "id", "file", {}); + const nested = builder.node(["CanNode"], "id", "file/", {}); + const slash = builder.node(["CanNode"], "id", "file/x", {}); + + builder.edge("TS_CALLS", file, target); + builder.edge("TS_CALLS", nested, target); + builder.edge("TS_CALLS", slash, target); + + expect(builder.finish().edges.map((edge) => edge.from.value)).toEqual([ + "file/", + "file/x", + "file", + ]); +});