refactor(lbug): keep relation pair parsing in router

This commit is contained in:
Shining 2026-07-30 11:58:10 +08:00
parent b730f1eb62
commit 9749092f0d
5 changed files with 40 additions and 21 deletions

View file

@ -17,12 +17,14 @@ import { createWriteStream, WriteStream } from 'fs';
import path from 'path';
import type { GraphNode, GraphRelationship } from 'gitnexus-shared';
import { KnowledgeGraph } from '../graph/types.js';
import { NodeTableName, NODE_TABLES, RELATION_SCHEMA_PAIRS } from './schema.js';
import { RelPairRouter } from './rel-pair-routing.js';
import { NodeTableName, NODE_TABLES, RELATION_SCHEMA } from './schema.js';
import { parseRelationSchemaPairs, RelPairRouter } from './rel-pair-routing.js';
import { parseTruthyEnv } from '../ingestion/utils/env.js';
import { SYMBOL_NODE_LABELS } from '../ingestion/utils/symbol-labels.js';
import { applyCjkSegmentationIfEnabled } from '../search/cjk-segmentation.js';
const DECLARED_RELATION_PAIRS = parseRelationSchemaPairs(RELATION_SCHEMA);
/**
* Deterministic output ordering — optional (out-of-core / windowed-resolve
* enabler). When `GITNEXUS_SORT_GRAPH_OUTPUT` is set, nodes and relationships
@ -789,7 +791,7 @@ export const streamAllCSVsToDisk = async (
csvDir,
REL_CSV_HEADER,
new Set<string>(NODE_TABLES),
RELATION_SCHEMA_PAIRS,
DECLARED_RELATION_PAIRS,
);
try {
let emitted = 0;

View file

@ -48,6 +48,22 @@ export const getNodeLabel = (nodeId: string): string => {
return nodeId.split(':')[0];
};
/**
* Extract the FROM→TO pairs accepted by a relationship DDL.
*
* This belongs at the routing boundary: schema.ts owns the DDL, while the CSV
* router owns the fail-fast check that prevents writing a pair LadybugDB cannot
* COPY. Backticks quote schema labels and are not part of the graph label.
*/
export const parseRelationSchemaPairs = (relationSchema: string): ReadonlySet<string> =>
new Set(
[
...relationSchema.matchAll(
/\bFROM\s+`?([A-Za-z][A-Za-z0-9_]*)`?\s+TO\s+`?([A-Za-z][A-Za-z0-9_]*)`?/g,
),
].map((match) => `${match[1]}|${match[2]}`),
);
export interface RelPairMeta {
csvPath: string;
rows: number;

View file

@ -462,21 +462,6 @@ CREATE REL TABLE ${REL_TABLE_NAME} (
step INT32
)`;
/**
* Declared FROM→TO label pairs for the single relationship table.
*
* Derived from the DDL itself so the CSV router and LadybugDB schema cannot
* drift into separate allowlists. Backticks are schema quoting, not part of a
* node label.
*/
export const RELATION_SCHEMA_PAIRS: ReadonlySet<string> = new Set(
[
...RELATION_SCHEMA.matchAll(
/\bFROM\s+`?([A-Za-z][A-Za-z0-9_]*)`?\s+TO\s+`?([A-Za-z][A-Za-z0-9_]*)`?/g,
),
].map((match) => `${match[1]}|${match[2]}`),
);
// ============================================================================
// EMBEDDING TABLE SCHEMA
// Separate table for vector storage to avoid copy-on-write overhead

View file

@ -3,7 +3,11 @@ import { EventEmitter } from 'events';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { RelPairRouter, getNodeLabel } from '../../src/core/lbug/rel-pair-routing.js';
import {
RelPairRouter,
getNodeLabel,
parseRelationSchemaPairs,
} from '../../src/core/lbug/rel-pair-routing.js';
/**
* Unit tests for RelPairRouter (#2203 U2) — the production per-pair emit path.
@ -98,6 +102,20 @@ describe('getNodeLabel', () => {
});
});
describe('parseRelationSchemaPairs', () => {
it('extracts plain and quoted FROM→TO labels for router validation', () => {
expect(
parseRelationSchemaPairs(`
CREATE REL TABLE CodeRelation(
FROM Class TO CodeElement,
FROM \`Enum\` TO \`TypeAlias\`,
type STRING
)
`),
).toEqual(new Set(['Class|CodeElement', 'Enum|TypeAlias']));
});
});
describe('RelPairRouter', () => {
it('routes valid edges to per-pair files (header first) and skips invalid-label edges', async () => {
const streams: MockWriteStream[] = [];

View file

@ -19,7 +19,6 @@ import {
PROCESS_SCHEMA,
BASICBLOCK_SCHEMA,
RELATION_SCHEMA,
RELATION_SCHEMA_PAIRS,
EMBEDDING_SCHEMA,
CREATE_VECTOR_INDEX_QUERY,
} from '../../src/core/lbug/schema.js';
@ -211,7 +210,6 @@ describe('LadybugDB Schema', () => {
it('persists consumer Class injection edges to synthetic provider declarations', () => {
expect(RELATION_SCHEMA).toContain('FROM Class TO CodeElement');
expect(RELATION_SCHEMA_PAIRS.has('Class|CodeElement')).toBe(true);
});
it('has all FROM/TO pairs needed for HAS_METHOD edges', () => {