mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
refactor(cli): resolve relationship dialect at scan boundary
This commit is contained in:
parent
c0932c98cb
commit
f000221f78
4 changed files with 58 additions and 17 deletions
|
|
@ -331,6 +331,27 @@ describe('local scan enrichment', () => {
|
|||
expect(scanConnector.introspect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('fails when connector driver and snapshot driver differ', async () => {
|
||||
const mismatchedConnector: KtxScanConnector = {
|
||||
...connector(),
|
||||
driver: 'mysql',
|
||||
};
|
||||
|
||||
await expect(
|
||||
runLocalScanEnrichment({
|
||||
connectionId: 'warehouse',
|
||||
mode: 'relationships',
|
||||
detectRelationships: true,
|
||||
connector: mismatchedConnector,
|
||||
snapshot,
|
||||
context: { runId: 'scan-run-driver-mismatch' },
|
||||
providers: null,
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
'ktx scan connector driver "mysql" does not match snapshot driver "postgres" for connection "warehouse"',
|
||||
);
|
||||
});
|
||||
|
||||
it('runs deterministic relationship detection for relationship scans', async () => {
|
||||
const result = await runLocalScanEnrichment({
|
||||
connectionId: 'warehouse',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import pLimit from 'p-limit';
|
||||
import type { KtxLlmRuntimePort } from '../../context/llm/runtime-port.js';
|
||||
import { getDialectForDriver } from '../connections/dialects.js';
|
||||
import { buildDefaultKtxProjectConfig, type KtxScanRelationshipConfig } from '../project/config.js';
|
||||
import { KtxDescriptionGenerator } from './description-generation.js';
|
||||
import { buildKtxColumnEmbeddingText } from './embedding-text.js';
|
||||
|
|
@ -118,6 +119,18 @@ function targetMatchesForeignKey(table: KtxEnrichedTable, foreignKey: KtxSchemaF
|
|||
);
|
||||
}
|
||||
|
||||
function assertConnectorDriverMatchesSnapshot(input: {
|
||||
connector: KtxScanConnector;
|
||||
snapshot: KtxSchemaSnapshot;
|
||||
connectionId: string;
|
||||
}): void {
|
||||
if (input.connector.driver !== input.snapshot.driver) {
|
||||
throw new Error(
|
||||
`ktx scan connector driver "${input.connector.driver}" does not match snapshot driver "${input.snapshot.driver}" for connection "${input.connectionId}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function formalRelationshipsFromSnapshot(
|
||||
snapshot: KtxSchemaSnapshot,
|
||||
tables: readonly KtxEnrichedTable[],
|
||||
|
|
@ -468,6 +481,12 @@ export async function runLocalScanEnrichment(
|
|||
));
|
||||
await progress?.update(0.05, `Loaded schema snapshot with ${snapshot.tables.length} tables`);
|
||||
|
||||
assertConnectorDriverMatchesSnapshot({
|
||||
connector: input.connector,
|
||||
snapshot,
|
||||
connectionId: input.connectionId,
|
||||
});
|
||||
const dialect = getDialectForDriver(snapshot.driver);
|
||||
const now = input.now ?? (() => new Date());
|
||||
const state = completedKtxScanEnrichmentStateSummary();
|
||||
const syncId = input.syncId ?? input.context.runId;
|
||||
|
|
@ -575,7 +594,7 @@ export async function runLocalScanEnrichment(
|
|||
await relationshipProgress?.update(0, 'Detecting relationships');
|
||||
const detection = await discoverKtxRelationships({
|
||||
connectionId: input.connectionId,
|
||||
driver: snapshot.driver,
|
||||
dialect,
|
||||
connector: input.connector,
|
||||
schema,
|
||||
context: input.context,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Database from 'better-sqlite3';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { KtxLlmRuntimePort } from '../../context/llm/runtime-port.js';
|
||||
import { getDialectForDriver } from '../connections/dialects.js';
|
||||
import { buildDefaultKtxProjectConfig } from '../project/config.js';
|
||||
import { snapshotToKtxEnrichedSchema } from './local-enrichment.js';
|
||||
import {
|
||||
|
|
@ -308,7 +309,7 @@ describe('production relationship discovery', () => {
|
|||
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: connector(executor),
|
||||
schema: snapshotToKtxEnrichedSchema(snapshot()),
|
||||
context: { runId: 'relationship-run-1' },
|
||||
|
|
@ -347,7 +348,7 @@ describe('production relationship discovery', () => {
|
|||
const schema = naturalKeySnapshot();
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: {
|
||||
...connector(executor),
|
||||
introspect: async () => schema,
|
||||
|
|
@ -397,7 +398,7 @@ describe('production relationship discovery', () => {
|
|||
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: {
|
||||
...connector(executor),
|
||||
introspect: async () => sourceSnapshot,
|
||||
|
|
@ -430,7 +431,7 @@ describe('production relationship discovery', () => {
|
|||
it('keeps candidates review-only when read-only SQL is unavailable', async () => {
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: connector(null),
|
||||
schema: snapshotToKtxEnrichedSchema(snapshot()),
|
||||
context: { runId: 'relationship-run-no-sql' },
|
||||
|
|
@ -456,7 +457,7 @@ describe('production relationship discovery', () => {
|
|||
const sourceSnapshot = declaredForeignKeySnapshot();
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: connector(null),
|
||||
schema: snapshotToKtxEnrichedSchema(sourceSnapshot),
|
||||
context: { runId: 'formal-metadata-no-sql' },
|
||||
|
|
@ -503,7 +504,7 @@ describe('production relationship discovery', () => {
|
|||
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: connector(executor),
|
||||
schema: snapshotToKtxEnrichedSchema(llmOnlyRelationshipSnapshot()),
|
||||
context: { runId: 'llm-relationship-orchestrator' },
|
||||
|
|
@ -543,7 +544,7 @@ describe('production relationship discovery', () => {
|
|||
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: connector(executor),
|
||||
schema: snapshotToKtxEnrichedSchema(snapshot()),
|
||||
context: { runId: 'configured-thresholds' },
|
||||
|
|
@ -604,7 +605,7 @@ describe('production relationship discovery', () => {
|
|||
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: 'warehouse',
|
||||
driver: 'sqlite',
|
||||
dialect: getDialectForDriver('sqlite'),
|
||||
connector: {
|
||||
...connector(executor),
|
||||
introspect: async () => richSnapshot,
|
||||
|
|
@ -658,7 +659,7 @@ describe('production relationship discovery', () => {
|
|||
|
||||
const result = await discoverKtxRelationships({
|
||||
connectionId: maskedSnapshot.connectionId,
|
||||
driver: maskedSnapshot.driver,
|
||||
dialect: getDialectForDriver(maskedSnapshot.driver),
|
||||
connector: testConnector,
|
||||
schema: snapshotToKtxEnrichedSchema(maskedSnapshot, new Map()),
|
||||
context: { runId: 'test:production-composite' },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { KtxLlmRuntimePort } from '../../context/llm/runtime-port.js';
|
||||
import type { KtxDialect } from '../connections/dialects.js';
|
||||
import type { KtxScanRelationshipConfig } from '../project/config.js';
|
||||
import type { KtxEnrichedRelationship, KtxEnrichedSchema, KtxRelationshipUpdate } from './enrichment-types.js';
|
||||
import {
|
||||
|
|
@ -24,7 +25,6 @@ import {
|
|||
} from './relationship-profiling.js';
|
||||
import { validateKtxRelationshipDiscoveryCandidates } from './relationship-validation.js';
|
||||
import type {
|
||||
KtxConnectionDriver,
|
||||
KtxScanConnector,
|
||||
KtxScanContext,
|
||||
KtxScanEnrichmentSummary,
|
||||
|
|
@ -34,7 +34,7 @@ import type {
|
|||
|
||||
export interface DiscoverKtxRelationshipsInput {
|
||||
connectionId: string;
|
||||
driver: KtxConnectionDriver;
|
||||
dialect: KtxDialect;
|
||||
connector: KtxScanConnector;
|
||||
schema: KtxEnrichedSchema;
|
||||
context: KtxScanContext;
|
||||
|
|
@ -122,7 +122,7 @@ function compositeSummary(relationships: readonly KtxCompositeRelationshipCandid
|
|||
|
||||
async function detectCompositeRelationships(input: {
|
||||
connectionId: string;
|
||||
driver: DiscoverKtxRelationshipsInput['driver'];
|
||||
dialect: KtxDialect;
|
||||
schema: KtxEnrichedSchema;
|
||||
profile: KtxRelationshipProfileArtifact;
|
||||
executor: KtxRelationshipReadOnlyExecutor | null;
|
||||
|
|
@ -135,7 +135,7 @@ async function detectCompositeRelationships(input: {
|
|||
try {
|
||||
const compositeDetection = await discoverKtxCompositeRelationships({
|
||||
connectionId: input.connectionId,
|
||||
driver: input.driver,
|
||||
driver: input.dialect.type,
|
||||
schema: input.schema,
|
||||
profiles: input.profile,
|
||||
executor: input.executor,
|
||||
|
|
@ -223,7 +223,7 @@ export async function discoverKtxRelationships(
|
|||
const profileCache = createKtxRelationshipProfileCache();
|
||||
const profile = await profileKtxRelationshipSchema({
|
||||
connectionId: input.connectionId,
|
||||
driver: input.driver,
|
||||
driver: input.dialect.type,
|
||||
schema: input.schema,
|
||||
executor,
|
||||
ctx: input.context,
|
||||
|
|
@ -256,7 +256,7 @@ export async function discoverKtxRelationships(
|
|||
warnings.push(...llmProposalResult.warnings);
|
||||
const validated = await validateKtxRelationshipDiscoveryCandidates({
|
||||
connectionId: input.connectionId,
|
||||
driver: input.driver,
|
||||
driver: input.dialect.type,
|
||||
candidates,
|
||||
profiles: profile,
|
||||
executor,
|
||||
|
|
@ -282,7 +282,7 @@ export async function discoverKtxRelationships(
|
|||
});
|
||||
const compositeRelationships = await detectCompositeRelationships({
|
||||
connectionId: input.connectionId,
|
||||
driver: input.driver,
|
||||
dialect: input.dialect,
|
||||
schema: input.schema,
|
||||
profile,
|
||||
executor,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue