@memberjunction/db-auto-doc 6.2.0-edge.4 → 6.2.0-edge.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/dist/api/DBAutoDocAPI.d.ts +2 -0
- package/dist/api/DBAutoDocAPI.d.ts.map +1 -1
- package/dist/api/DBAutoDocAPI.js +2 -1
- package/dist/api/DBAutoDocAPI.js.map +1 -1
- package/dist/commands/analyze.d.ts +1 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +6 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/export.d.ts +28 -19
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +91 -12
- package/dist/commands/export.js.map +1 -1
- package/dist/core/AnalysisEngine.d.ts +39 -6
- package/dist/core/AnalysisEngine.d.ts.map +1 -1
- package/dist/core/AnalysisEngine.js +144 -50
- package/dist/core/AnalysisEngine.js.map +1 -1
- package/dist/core/AnalysisOrchestrator.d.ts +20 -1
- package/dist/core/AnalysisOrchestrator.d.ts.map +1 -1
- package/dist/core/AnalysisOrchestrator.js +144 -28
- package/dist/core/AnalysisOrchestrator.js.map +1 -1
- package/dist/core/PKProposalGate.d.ts +22 -0
- package/dist/core/PKProposalGate.d.ts.map +1 -0
- package/dist/core/PKProposalGate.js +52 -0
- package/dist/core/PKProposalGate.js.map +1 -0
- package/dist/database/Database.d.ts +8 -0
- package/dist/database/Database.d.ts.map +1 -1
- package/dist/database/Database.js +23 -1
- package/dist/database/Database.js.map +1 -1
- package/dist/discovery/ColumnNormalizer.d.ts +31 -0
- package/dist/discovery/ColumnNormalizer.d.ts.map +1 -1
- package/dist/discovery/ColumnNormalizer.js +68 -2
- package/dist/discovery/ColumnNormalizer.js.map +1 -1
- package/dist/discovery/Composer.d.ts +33 -2
- package/dist/discovery/Composer.d.ts.map +1 -1
- package/dist/discovery/Composer.js +126 -5
- package/dist/discovery/Composer.js.map +1 -1
- package/dist/discovery/DiscoveryEngine.d.ts +3 -0
- package/dist/discovery/DiscoveryEngine.d.ts.map +1 -1
- package/dist/discovery/DiscoveryEngine.js +1 -1
- package/dist/discovery/DiscoveryEngine.js.map +1 -1
- package/dist/discovery/EmbeddingProvider.d.ts +6 -1
- package/dist/discovery/EmbeddingProvider.d.ts.map +1 -1
- package/dist/discovery/EmbeddingProvider.js +11 -1
- package/dist/discovery/EmbeddingProvider.js.map +1 -1
- package/dist/discovery/EnumCandidateGate.d.ts +19 -0
- package/dist/discovery/EnumCandidateGate.d.ts.map +1 -1
- package/dist/discovery/EnumCandidateGate.js +47 -5
- package/dist/discovery/EnumCandidateGate.js.map +1 -1
- package/dist/discovery/FKGraphWalker.d.ts +35 -0
- package/dist/discovery/FKGraphWalker.d.ts.map +1 -1
- package/dist/discovery/FKGraphWalker.js +113 -12
- package/dist/discovery/FKGraphWalker.js.map +1 -1
- package/dist/discovery/IndexKeyEvidence.d.ts +36 -0
- package/dist/discovery/IndexKeyEvidence.d.ts.map +1 -0
- package/dist/discovery/IndexKeyEvidence.js +42 -0
- package/dist/discovery/IndexKeyEvidence.js.map +1 -0
- package/dist/discovery/JoinProbe.d.ts +148 -0
- package/dist/discovery/JoinProbe.d.ts.map +1 -0
- package/dist/discovery/JoinProbe.js +233 -0
- package/dist/discovery/JoinProbe.js.map +1 -0
- package/dist/discovery/LLMDiscoveryValidator.d.ts +10 -1
- package/dist/discovery/LLMDiscoveryValidator.d.ts.map +1 -1
- package/dist/discovery/LLMDiscoveryValidator.js +44 -12
- package/dist/discovery/LLMDiscoveryValidator.js.map +1 -1
- package/dist/discovery/LLMSanityChecker.d.ts.map +1 -1
- package/dist/discovery/LLMSanityChecker.js +10 -1
- package/dist/discovery/LLMSanityChecker.js.map +1 -1
- package/dist/discovery/OrganicKeyDetector.d.ts +29 -1
- package/dist/discovery/OrganicKeyDetector.d.ts.map +1 -1
- package/dist/discovery/OrganicKeyDetector.js +34 -5
- package/dist/discovery/OrganicKeyDetector.js.map +1 -1
- package/dist/discovery/OrganicKeyTranslator.d.ts +24 -5
- package/dist/discovery/OrganicKeyTranslator.d.ts.map +1 -1
- package/dist/discovery/OrganicKeyTranslator.js +32 -14
- package/dist/discovery/OrganicKeyTranslator.js.map +1 -1
- package/dist/discovery/PKDetector.d.ts +9 -0
- package/dist/discovery/PKDetector.d.ts.map +1 -1
- package/dist/discovery/PKDetector.js +86 -8
- package/dist/discovery/PKDetector.js.map +1 -1
- package/dist/discovery/SemanticPhase.d.ts +25 -0
- package/dist/discovery/SemanticPhase.d.ts.map +1 -1
- package/dist/discovery/SemanticPhase.js +55 -11
- package/dist/discovery/SemanticPhase.js.map +1 -1
- package/dist/discovery/StructuralPhase.d.ts +27 -5
- package/dist/discovery/StructuralPhase.d.ts.map +1 -1
- package/dist/discovery/StructuralPhase.js +42 -9
- package/dist/discovery/StructuralPhase.js.map +1 -1
- package/dist/discovery/TransitiveBridgeDetector.d.ts +5 -1
- package/dist/discovery/TransitiveBridgeDetector.d.ts.map +1 -1
- package/dist/discovery/TransitiveBridgeDetector.js +25 -3
- package/dist/discovery/TransitiveBridgeDetector.js.map +1 -1
- package/dist/discovery/embedding-provider-default.d.ts +25 -0
- package/dist/discovery/embedding-provider-default.d.ts.map +1 -0
- package/dist/discovery/embedding-provider-default.js +47 -0
- package/dist/discovery/embedding-provider-default.js.map +1 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts +63 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -1
- package/dist/drivers/BaseAutoDocDriver.js +3 -5
- package/dist/drivers/BaseAutoDocDriver.js.map +1 -1
- package/dist/drivers/MySQLDriver.d.ts +21 -1
- package/dist/drivers/MySQLDriver.d.ts.map +1 -1
- package/dist/drivers/MySQLDriver.js +64 -0
- package/dist/drivers/MySQLDriver.js.map +1 -1
- package/dist/drivers/PostgreSQLDriver.d.ts +43 -2
- package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -1
- package/dist/drivers/PostgreSQLDriver.js +132 -19
- package/dist/drivers/PostgreSQLDriver.js.map +1 -1
- package/dist/drivers/SQLServerDriver.d.ts +28 -1
- package/dist/drivers/SQLServerDriver.d.ts.map +1 -1
- package/dist/drivers/SQLServerDriver.js +108 -1
- package/dist/drivers/SQLServerDriver.js.map +1 -1
- package/dist/drivers/probeErrors.d.ts +30 -0
- package/dist/drivers/probeErrors.d.ts.map +1 -0
- package/dist/drivers/probeErrors.js +94 -0
- package/dist/drivers/probeErrors.js.map +1 -0
- package/dist/generators/AdditionalSchemaInfoGenerator.d.ts +34 -18
- package/dist/generators/AdditionalSchemaInfoGenerator.d.ts.map +1 -1
- package/dist/generators/AdditionalSchemaInfoGenerator.js +130 -8
- package/dist/generators/AdditionalSchemaInfoGenerator.js.map +1 -1
- package/dist/generators/ErdClusterGenerator.d.ts +44 -0
- package/dist/generators/ErdClusterGenerator.d.ts.map +1 -0
- package/dist/generators/ErdClusterGenerator.js +216 -0
- package/dist/generators/ErdClusterGenerator.js.map +1 -0
- package/dist/generators/ErdClusterer.d.ts +130 -0
- package/dist/generators/ErdClusterer.d.ts.map +1 -0
- package/dist/generators/ErdClusterer.js +308 -0
- package/dist/generators/ErdClusterer.js.map +1 -0
- package/dist/generators/HTMLGenerator.d.ts +6 -0
- package/dist/generators/HTMLGenerator.d.ts.map +1 -1
- package/dist/generators/HTMLGenerator.js +41 -6
- package/dist/generators/HTMLGenerator.js.map +1 -1
- package/dist/generators/MermaidGenerator.d.ts +3 -0
- package/dist/generators/MermaidGenerator.d.ts.map +1 -1
- package/dist/generators/MermaidGenerator.js +4 -0
- package/dist/generators/MermaidGenerator.js.map +1 -1
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/prompts/PromptEngine.d.ts.map +1 -1
- package/dist/prompts/PromptEngine.js +10 -1
- package/dist/prompts/PromptEngine.js.map +1 -1
- package/dist/types/analysis.d.ts +2 -0
- package/dist/types/analysis.d.ts.map +1 -1
- package/dist/types/config.d.ts +128 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +7 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/discovery.d.ts +41 -0
- package/dist/types/discovery.d.ts.map +1 -1
- package/dist/types/state.d.ts +29 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/utils/call-deadline.d.ts +43 -0
- package/dist/utils/call-deadline.d.ts.map +1 -0
- package/dist/utils/call-deadline.js +81 -0
- package/dist/utils/call-deadline.js.map +1 -0
- package/dist/utils/config-loader.d.ts +5 -0
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +37 -0
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/index-hints.d.ts +43 -0
- package/dist/utils/index-hints.d.ts.map +1 -0
- package/dist/utils/index-hints.js +80 -0
- package/dist/utils/index-hints.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/llm-factory.d.ts +1 -0
- package/dist/utils/llm-factory.d.ts.map +1 -1
- package/dist/utils/llm-factory.js +6 -1
- package/dist/utils/llm-factory.js.map +1 -1
- package/dist/utils/seed-context.d.ts +23 -0
- package/dist/utils/seed-context.d.ts.map +1 -0
- package/dist/utils/seed-context.js +38 -0
- package/dist/utils/seed-context.js.map +1 -0
- package/package.json +5 -5
- package/prompts/fk-pruning-holistic.md +7 -0
- package/prompts/fk-pruning-table.md +20 -1
- package/prompts/pk-pruning-holistic.md +7 -0
- package/prompts/pk-pruning-table.md +17 -0
- package/prompts/table-analysis.md +11 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Uses LLM intelligence to review and reject obviously wrong PK/FK candidates
|
|
4
4
|
* This is a MACRO-LEVEL review that happens once after statistical detection
|
|
5
5
|
*/
|
|
6
|
+
import { ResolveCallTimeoutMs, WithCallDeadline } from '../utils/call-deadline.js';
|
|
6
7
|
import { CreateLLMInstance } from '../utils/llm-factory.js';
|
|
7
8
|
export class LLMSanityChecker {
|
|
8
9
|
constructor(aiConfig, statsCache) {
|
|
@@ -37,7 +38,15 @@ export class LLMSanityChecker {
|
|
|
37
38
|
responseFormat: 'JSON'
|
|
38
39
|
};
|
|
39
40
|
console.log(`[LLMSanityChecker] Calling LLM for sanity check...`);
|
|
40
|
-
|
|
41
|
+
// Bounded. This call reviews the ENTIRE candidate set in one request, so its prompt grows with
|
|
42
|
+
// the schema and it is the single largest call the discovery phase makes — and it had no
|
|
43
|
+
// deadline, no retry and no failover, so a stall here parked the whole run.
|
|
44
|
+
//
|
|
45
|
+
// The throw is deliberately NOT caught here: the caller in DiscoveryEngine already treats an
|
|
46
|
+
// exception from this checker as fatal to the iteration, whereas a returned failure is
|
|
47
|
+
// swallowed into "no candidates were invalid" — which would silently pass every statistically
|
|
48
|
+
// derived key through unreviewed. A timeout must not look like an all-clear.
|
|
49
|
+
const chatResult = await WithCallDeadline(ResolveCallTimeoutMs(this.aiConfig.callTimeoutMs), 'PK/FK sanity check', signal => this.llm.ChatCompletion({ ...params, cancellationToken: signal }));
|
|
41
50
|
if (!chatResult.success) {
|
|
42
51
|
console.warn(`[LLMSanityChecker] LLM call failed: ${chatResult.errorMessage}`);
|
|
43
52
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMSanityChecker.js","sourceRoot":"","sources":["../../src/discovery/LLMSanityChecker.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"LLMSanityChecker.js","sourceRoot":"","sources":["../../src/discovery/LLMSanityChecker.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,7 +20,9 @@ import { DatabaseDocumentation } from '../types/state.js';
|
|
|
20
20
|
import { OrganicKeyCluster, OrganicKeyDetectionPhase } from '../types/organic-keys.js';
|
|
21
21
|
import { ProgressCallback } from './SemanticPhase.js';
|
|
22
22
|
import { BridgeViewProvider } from './BridgeViewSQLGenerator.js';
|
|
23
|
+
import { ClusterVerification } from './Composer.js';
|
|
23
24
|
import { DetectedOrganicKeysOutput } from './OrganicKeyTranslator.js';
|
|
25
|
+
import { KeyVerifier } from './JoinProbe.js';
|
|
24
26
|
export interface OrganicKeyDetectionResult {
|
|
25
27
|
clusters: OrganicKeyCluster[];
|
|
26
28
|
output: DetectedOrganicKeysOutput;
|
|
@@ -37,20 +39,46 @@ export interface OrganicKeyDetectionResult {
|
|
|
37
39
|
outputKeys: number;
|
|
38
40
|
outputSpokes: number;
|
|
39
41
|
transitiveBridges: number;
|
|
42
|
+
/** Clusters the value-overlap probe refuted outright. */
|
|
43
|
+
clustersDroppedUnverified: number;
|
|
44
|
+
/** Members dropped for not sharing the anchor's value space. */
|
|
45
|
+
membersDroppedUnverified: number;
|
|
46
|
+
/** Concepts not emitted because no member owns the value as its table's primary key. */
|
|
47
|
+
conceptsSkippedUnanchored: number;
|
|
48
|
+
/** Probes spent, and the cap they ran under. */
|
|
49
|
+
probesUsed: number;
|
|
50
|
+
probesAllowed: number;
|
|
40
51
|
};
|
|
52
|
+
/** Per-cluster probe record, so a dropped key can be explained rather than just missing. */
|
|
53
|
+
Verification: ClusterVerification[];
|
|
41
54
|
}
|
|
42
55
|
export interface DetectorRunOptions {
|
|
43
56
|
OnProgress?: ProgressCallback;
|
|
44
57
|
}
|
|
58
|
+
/** Emit-time behaviour for the detector. */
|
|
59
|
+
export interface OrganicKeyEmitOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Set `AutoCreateRelatedViewOnForm` on emitted keys. Default false — a
|
|
62
|
+
* machine-proposed key should not silently create a grid per spoke on every form.
|
|
63
|
+
*/
|
|
64
|
+
AutoCreateRelatedViewOnForm?: boolean;
|
|
65
|
+
}
|
|
45
66
|
export declare class OrganicKeyDetector {
|
|
46
67
|
private readonly config;
|
|
47
68
|
private readonly aiConfig;
|
|
48
69
|
private readonly databaseProvider?;
|
|
70
|
+
private readonly keyVerifier;
|
|
71
|
+
private readonly emitOptions;
|
|
49
72
|
/**
|
|
50
73
|
* @param databaseProvider - Platform of the analyzed database. Bridge-view SQL is emitted in
|
|
51
74
|
* its dialect because CodeGen executes it verbatim. Default SQL Server.
|
|
75
|
+
* @param keyVerifier - Probes each cluster member against the cluster anchor before
|
|
76
|
+
* the key is emitted. Pass the SAME instance the analysis engine
|
|
77
|
+
* got, so one probe budget covers the whole run. When null, keys
|
|
78
|
+
* are emitted unverified — the previous behaviour — and the
|
|
79
|
+
* per-cluster record says they were never checked.
|
|
52
80
|
*/
|
|
53
|
-
constructor(config: OrganicKeyDetectionConfig, aiConfig: AIConfig, databaseProvider?: BridgeViewProvider);
|
|
81
|
+
constructor(config: OrganicKeyDetectionConfig, aiConfig: AIConfig, databaseProvider?: BridgeViewProvider, keyVerifier?: KeyVerifier | null, emitOptions?: OrganicKeyEmitOptions);
|
|
54
82
|
Detect(state: DatabaseDocumentation, opts?: DetectorRunOptions): Promise<OrganicKeyDetectionResult>;
|
|
55
83
|
/** @deprecated Use {@link Detect}. */
|
|
56
84
|
detect(state: DatabaseDocumentation, opts?: DetectorRunOptions): Promise<OrganicKeyDetectionResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganicKeyDetector.d.ts","sourceRoot":"","sources":["../../src/discovery/OrganicKeyDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,EAAoB,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"OrganicKeyDetector.d.ts","sourceRoot":"","sources":["../../src/discovery/OrganicKeyDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,EAAoB,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAW,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,MAAM,EAAE,yBAAyB,CAAC;IAClC,KAAK,EAAE,wBAAwB,CAAC;IAChC,OAAO,EAAE;QACL,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,2BAA2B,EAAE,MAAM,CAAC;QACpC,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,yDAAyD;QACzD,yBAAyB,EAAE,MAAM,CAAC;QAClC,gEAAgE;QAChE,wBAAwB,EAAE,MAAM,CAAC;QACjC,wFAAwF;QACxF,yBAAyB,EAAE,MAAM,CAAC;QAClC,gDAAgD;QAChD,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,4FAA4F;IAC5F,YAAY,EAAE,mBAAmB,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACzC;AAED,qBAAa,kBAAkB;IAWvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAdhC;;;;;;;;OAQG;gBAEkB,MAAM,EAAE,yBAAyB,EACjC,QAAQ,EAAE,QAAQ,EAClB,gBAAgB,CAAC,EAAE,kBAAkB,EACrC,WAAW,GAAE,WAAW,GAAG,IAAW,EACtC,WAAW,GAAE,qBAA0B;IAG/C,MAAM,CACf,KAAK,EAAE,qBAAqB,EAC5B,IAAI,GAAE,kBAAuB,GAC9B,OAAO,CAAC,yBAAyB,CAAC;IA6ErC,sCAAsC;IACzB,MAAM,CACf,KAAK,EAAE,qBAAqB,EAC5B,IAAI,GAAE,kBAAuB,GAC9B,OAAO,CAAC,yBAAyB,CAAC;IAIrC,OAAO,CAAC,YAAY;CAMvB"}
|
|
@@ -22,20 +22,43 @@ export class OrganicKeyDetector {
|
|
|
22
22
|
/**
|
|
23
23
|
* @param databaseProvider - Platform of the analyzed database. Bridge-view SQL is emitted in
|
|
24
24
|
* its dialect because CodeGen executes it verbatim. Default SQL Server.
|
|
25
|
+
* @param keyVerifier - Probes each cluster member against the cluster anchor before
|
|
26
|
+
* the key is emitted. Pass the SAME instance the analysis engine
|
|
27
|
+
* got, so one probe budget covers the whole run. When null, keys
|
|
28
|
+
* are emitted unverified — the previous behaviour — and the
|
|
29
|
+
* per-cluster record says they were never checked.
|
|
25
30
|
*/
|
|
26
|
-
constructor(config, aiConfig, databaseProvider) {
|
|
31
|
+
constructor(config, aiConfig, databaseProvider, keyVerifier = null, emitOptions = {}) {
|
|
27
32
|
this.config = config;
|
|
28
33
|
this.aiConfig = aiConfig;
|
|
29
34
|
this.databaseProvider = databaseProvider;
|
|
35
|
+
this.keyVerifier = keyVerifier;
|
|
36
|
+
this.emitOptions = emitOptions;
|
|
30
37
|
}
|
|
31
38
|
async Detect(state, opts = {}) {
|
|
32
39
|
const progress = opts.OnProgress ?? (() => { });
|
|
33
40
|
const startedAt = new Date().toISOString();
|
|
34
41
|
const a = await RunSemanticPhase(state, this.config, this.aiConfig, progress);
|
|
35
|
-
const b = RunStructuralPhase(state, a.clusters, this.databaseProvider);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
const b = RunStructuralPhase(state, a.clusters, { Provider: this.databaseProvider });
|
|
43
|
+
// Say WHY when the phase produced nothing: "0 bridges" from a completed walk and
|
|
44
|
+
// "0 bridges" from a walk that never ran are different facts, and a truncated walk
|
|
45
|
+
// is a third. Reporting them separately is the difference between a schema with no
|
|
46
|
+
// transitive bridges and a schema whose walk we bounded.
|
|
47
|
+
progress(b.Summary.walked
|
|
48
|
+
? `structural: ${b.Summary.transitiveBridgesFound} bridges${b.Summary.truncationReasons ? ` (walk truncated: ${b.Summary.truncationReasons.join(', ')})` : ''}`
|
|
49
|
+
: `structural: skipped (${b.Summary.skipReason})`);
|
|
50
|
+
const c = await Compose(a.clusters, b.Bridges, this.keyVerifier, {
|
|
51
|
+
AutoCreateRelatedViewOnForm: this.emitOptions.AutoCreateRelatedViewOnForm,
|
|
52
|
+
});
|
|
53
|
+
// Report what the probe removed, not just what survived: "emitted 5 clusters" and
|
|
54
|
+
// "emitted 5 of 161, 156 refuted" are the same output and completely different
|
|
55
|
+
// facts about the schema.
|
|
56
|
+
const budget = this.keyVerifier ? this.keyVerifier.Budget : null;
|
|
57
|
+
progress(`compose: emitted ${c.Emitted}/${a.clusters.length} clusters (${c.Summary.outputKeys} keys, ${c.Summary.outputSpokes} spokes)`
|
|
58
|
+
+ (c.SkippedUnanchored > 0 ? `; skipped ${c.SkippedUnanchored} concepts with no primary-key owner` : '')
|
|
59
|
+
+ (budget
|
|
60
|
+
? `; probe refuted ${c.DroppedUnverified} clusters and ${c.DroppedMembers} members using ${budget.ProbesUsed}/${budget.ProbesAllowed} probes`
|
|
61
|
+
: '; keys NOT verified (no probe configured)'));
|
|
39
62
|
// Net additional clusters produced by the concept-name split (sub-clusters created
|
|
40
63
|
// beyond the raw clusterer output, counting both kept and dropped sub-clusters).
|
|
41
64
|
const splitClusterCount = Math.max(0, a.Summary.clustersFound + a.Summary.clustersDropped - a.Summary.clustersBeforeSplit);
|
|
@@ -69,7 +92,13 @@ export class OrganicKeyDetector {
|
|
|
69
92
|
outputKeys: c.Summary.outputKeys,
|
|
70
93
|
outputSpokes: c.Summary.outputSpokes,
|
|
71
94
|
transitiveBridges: b.Summary.transitiveBridgesFound,
|
|
95
|
+
clustersDroppedUnverified: c.DroppedUnverified,
|
|
96
|
+
membersDroppedUnverified: c.DroppedMembers,
|
|
97
|
+
conceptsSkippedUnanchored: c.SkippedUnanchored,
|
|
98
|
+
probesUsed: budget ? budget.ProbesUsed : 0,
|
|
99
|
+
probesAllowed: budget ? budget.ProbesAllowed : 0,
|
|
72
100
|
},
|
|
101
|
+
Verification: c.Verification,
|
|
73
102
|
};
|
|
74
103
|
}
|
|
75
104
|
/** @deprecated Use {@link Detect}. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganicKeyDetector.js","sourceRoot":"","sources":["../../src/discovery/OrganicKeyDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAAE,gBAAgB,EAAoB,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"OrganicKeyDetector.js","sourceRoot":"","sources":["../../src/discovery/OrganicKeyDetector.ts"],"names":[],"mappings":"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"}
|
|
@@ -60,16 +60,35 @@ export interface TransitiveSpokeInput {
|
|
|
60
60
|
relatedEntityJoinFieldName: string;
|
|
61
61
|
hubConcept?: string;
|
|
62
62
|
}
|
|
63
|
+
/** Emit-time options for {@link TranslateClusters}. */
|
|
64
|
+
export interface TranslateOptions {
|
|
65
|
+
/**
|
|
66
|
+
* Value for `AutoCreateRelatedViewOnForm` on every emitted key. Default FALSE.
|
|
67
|
+
*
|
|
68
|
+
* This was a hardcoded `true`, so every emitted key auto-created a related-record
|
|
69
|
+
* grid per spoke — around 22 grids per key on a real run, none of them asked for,
|
|
70
|
+
* each one a value-join on a column the detector had never verified. Opting in is
|
|
71
|
+
* the right default for a machine-proposed key.
|
|
72
|
+
*/
|
|
73
|
+
AutoCreateRelatedViewOnForm?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Called for each concept skipped because no member is its table's primary key, with the
|
|
76
|
+
* concept name and member count, so the caller can report what was not emitted.
|
|
77
|
+
*/
|
|
78
|
+
OnUnanchored?: (concept: string, members: number) => void;
|
|
79
|
+
}
|
|
63
80
|
/**
|
|
64
81
|
* Render gated clusters into PR #2193 JSON. Same-concept consolidation +
|
|
65
|
-
*
|
|
82
|
+
* hub-and-spoke fan-out + transitive spoke attachment. No thresholds.
|
|
66
83
|
*
|
|
67
|
-
* The caller (Composer) is responsible for filtering.
|
|
68
|
-
*
|
|
84
|
+
* The caller (Composer) is responsible for filtering by evidence. The one
|
|
85
|
+
* structural rule applied here: a concept is emitted only around the members
|
|
86
|
+
* that own it as a primary key (the hubs), and non-hub members are never
|
|
87
|
+
* linked to each other.
|
|
69
88
|
*/
|
|
70
|
-
export declare function TranslateClusters(clusters: OrganicKeyCluster[], transitiveSpokes?: TransitiveSpokeInput[]): DetectedOrganicKeysOutput;
|
|
89
|
+
export declare function TranslateClusters(clusters: OrganicKeyCluster[], transitiveSpokes?: TransitiveSpokeInput[], options?: TranslateOptions): DetectedOrganicKeysOutput;
|
|
71
90
|
/** @deprecated Use {@link TranslateClusters}. */
|
|
72
|
-
export declare function translateClusters(clusters: OrganicKeyCluster[], transitiveSpokes?: TransitiveSpokeInput[]): DetectedOrganicKeysOutput;
|
|
91
|
+
export declare function translateClusters(clusters: OrganicKeyCluster[], transitiveSpokes?: TransitiveSpokeInput[], options?: TranslateOptions): DetectedOrganicKeysOutput;
|
|
73
92
|
/** Tally the emit payload: number of schemas, tables, organic keys, and spokes it contains. */
|
|
74
93
|
export declare function CountOutputEntries(out: DetectedOrganicKeysOutput): {
|
|
75
94
|
schemas: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganicKeyTranslator.d.ts","sourceRoot":"","sources":["../../src/discovery/OrganicKeyTranslator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACH,iBAAiB,EAEjB,+BAA+B,EAGlC,MAAM,0BAA0B,CAAC;AAOlC,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,kIAAkI;AAClI,MAAM,WAAW,6BAA6B;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,yBAAyB,CAAC,EAAE,MAAM,EAAE,CAAC;IACrC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,yGAAyG;AACzG,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,qBAAqB,EAAE,+BAA+B,CAAC;IACvD,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,eAAe,EAAE,6BAA6B,EAAE,CAAC;CACpD;AAED,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAED,4EAA4E;AAC5E,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAAC;AAIhF,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,oBAAoB,CAAC;IACrC,yBAAyB,EAAE,MAAM,EAAE,CAAC;IACpC,yBAAyB,EAAE,MAAM,CAAC;IAClC,0BAA0B,EAAE,MAAM,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAID
|
|
1
|
+
{"version":3,"file":"OrganicKeyTranslator.d.ts","sourceRoot":"","sources":["../../src/discovery/OrganicKeyTranslator.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,15 +14,17 @@
|
|
|
14
14
|
* fan-out — deterministic equivalent of an LLM concept-merge pass.
|
|
15
15
|
*/
|
|
16
16
|
import { MemberColumns, IsCompoundMember, } from '../types/organic-keys.js';
|
|
17
|
-
// ─── Entry point — pure fan-out ─────────────────────────────────────────────
|
|
18
17
|
/**
|
|
19
18
|
* Render gated clusters into PR #2193 JSON. Same-concept consolidation +
|
|
20
|
-
*
|
|
19
|
+
* hub-and-spoke fan-out + transitive spoke attachment. No thresholds.
|
|
21
20
|
*
|
|
22
|
-
* The caller (Composer) is responsible for filtering.
|
|
23
|
-
*
|
|
21
|
+
* The caller (Composer) is responsible for filtering by evidence. The one
|
|
22
|
+
* structural rule applied here: a concept is emitted only around the members
|
|
23
|
+
* that own it as a primary key (the hubs), and non-hub members are never
|
|
24
|
+
* linked to each other.
|
|
24
25
|
*/
|
|
25
|
-
export function TranslateClusters(clusters, transitiveSpokes = []) {
|
|
26
|
+
export function TranslateClusters(clusters, transitiveSpokes = [], options = {}) {
|
|
27
|
+
const autoCreateRelatedView = options.AutoCreateRelatedViewOnForm ?? false;
|
|
26
28
|
// Step 1 — group by normalized canonical concept name.
|
|
27
29
|
const byConcept = new Map();
|
|
28
30
|
for (const cluster of clusters) {
|
|
@@ -58,13 +60,24 @@ export function TranslateClusters(clusters, transitiveSpokes = []) {
|
|
|
58
60
|
.slice()
|
|
59
61
|
.sort((a, b) => (b.confidence - a.confidence) || (b.members.length - a.members.length))[0];
|
|
60
62
|
const name = prettyConceptName(anchor.concept);
|
|
61
|
-
//
|
|
63
|
+
// Hubs are the members whose column is their table's primary key: the table that owns
|
|
64
|
+
// the value. Without one, every pair is a co-reference (two tables that both hold a
|
|
65
|
+
// customer key), which is many-to-many and multiplies rows when joined, so the concept
|
|
66
|
+
// is skipped rather than emitted as a join path.
|
|
67
|
+
const hubs = allMembers.filter((m) => m.isPrimaryKey);
|
|
68
|
+
if (hubs.length === 0) {
|
|
69
|
+
options.OnUnanchored?.(name, allMembers.length);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Step 3 — fan out, hub and spoke: a hub links to every other member (one-to-many);
|
|
73
|
+
// any other member links only to the hubs (many-to-one), never to another non-hub.
|
|
62
74
|
for (const owner of allMembers) {
|
|
63
75
|
const tableEntry = upsertTable(out, owner.schema, owner.table);
|
|
76
|
+
const targets = owner.isPrimaryKey ? allMembers : hubs;
|
|
64
77
|
const seenSpokes = new Set();
|
|
65
78
|
const spokes = [];
|
|
66
|
-
// Direct spokes
|
|
67
|
-
for (const target of
|
|
79
|
+
// Direct spokes.
|
|
80
|
+
for (const target of targets) {
|
|
68
81
|
if (target === owner)
|
|
69
82
|
continue;
|
|
70
83
|
const targetColumns = MemberColumns(target);
|
|
@@ -79,12 +92,17 @@ export function TranslateClusters(clusters, transitiveSpokes = []) {
|
|
|
79
92
|
DisplayName: `${target.schema}.${target.table}`,
|
|
80
93
|
});
|
|
81
94
|
}
|
|
82
|
-
// Transitive spokes — bridge views attached to matching hubs.
|
|
95
|
+
// Transitive spokes — bridge views attached to matching hubs. Only a hub owner gets
|
|
96
|
+
// them: a non-hub reaching another table through a bridge (orders -> registration via
|
|
97
|
+
// the customer key both hold) is the same co-reference the direct fan-out leaves out.
|
|
98
|
+
// A table the owner already links to directly is not added again through a bridge.
|
|
83
99
|
const ownerColumns = MemberColumns(owner);
|
|
84
|
-
const
|
|
100
|
+
const directTables = new Set(spokes.map((s) => `${s.SchemaName}.${s.TableName}`));
|
|
101
|
+
const matchingTransitive = !owner.isPrimaryKey ? [] : transitiveSpokes.filter((t) => t.hubSchema === owner.schema &&
|
|
85
102
|
t.hubTable === owner.table &&
|
|
86
103
|
t.hubKeyFields.length === ownerColumns.length &&
|
|
87
|
-
t.hubKeyFields.every((f, idx) => f === ownerColumns[idx])
|
|
104
|
+
t.hubKeyFields.every((f, idx) => f === ownerColumns[idx]) &&
|
|
105
|
+
!directTables.has(`${t.spokeSchema}.${t.spokeTable}`));
|
|
88
106
|
for (const t of matchingTransitive) {
|
|
89
107
|
spokes.push({
|
|
90
108
|
SchemaName: t.spokeSchema,
|
|
@@ -110,7 +128,7 @@ export function TranslateClusters(clusters, transitiveSpokes = []) {
|
|
|
110
128
|
MatchFieldNames: ownerColumns,
|
|
111
129
|
NormalizationStrategy: ownerNormalization,
|
|
112
130
|
CustomNormalizationExpression: ownerCustomExpression,
|
|
113
|
-
AutoCreateRelatedViewOnForm:
|
|
131
|
+
AutoCreateRelatedViewOnForm: autoCreateRelatedView,
|
|
114
132
|
RelatedEntities: spokes,
|
|
115
133
|
});
|
|
116
134
|
}
|
|
@@ -118,8 +136,8 @@ export function TranslateClusters(clusters, transitiveSpokes = []) {
|
|
|
118
136
|
return out;
|
|
119
137
|
}
|
|
120
138
|
/** @deprecated Use {@link TranslateClusters}. */
|
|
121
|
-
export function translateClusters(clusters, transitiveSpokes = []) {
|
|
122
|
-
return TranslateClusters(clusters, transitiveSpokes);
|
|
139
|
+
export function translateClusters(clusters, transitiveSpokes = [], options = {}) {
|
|
140
|
+
return TranslateClusters(clusters, transitiveSpokes, options);
|
|
123
141
|
}
|
|
124
142
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
125
143
|
function normalizeConceptName(name) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganicKeyTranslator.js","sourceRoot":"","sources":["../../src/discovery/OrganicKeyTranslator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAIH,aAAa,EACb,gBAAgB,GACnB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"OrganicKeyTranslator.js","sourceRoot":"","sources":["../../src/discovery/OrganicKeyTranslator.ts"],"names":[],"mappings":"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"}
|
|
@@ -77,5 +77,14 @@ export declare class PKDetector {
|
|
|
77
77
|
*/
|
|
78
78
|
private findConsecutivePKEligibleColumns;
|
|
79
79
|
private detectCompositeKeys;
|
|
80
|
+
/** Whether index-backed candidates are switched on (default true). */
|
|
81
|
+
private useIndexes;
|
|
82
|
+
/**
|
|
83
|
+
* Composite candidates from multi-column unique (and primary-key) indexes. A unique index allows repeated NULLs,
|
|
84
|
+
* so every column must be NOT NULL, or nullable with no nulls in a non-empty table.
|
|
85
|
+
*/
|
|
86
|
+
private indexBackedCompositeCandidates;
|
|
87
|
+
/** Adds candidates, keeping the higher-confidence one when the same column set is already present. */
|
|
88
|
+
private mergeCandidates;
|
|
80
89
|
}
|
|
81
90
|
//# sourceMappingURL=PKDetector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PKDetector.d.ts","sourceRoot":"","sources":["../../src/discovery/PKDetector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAoB,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAmD,MAAM,uBAAuB,CAAC;AACrG,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"PKDetector.d.ts","sourceRoot":"","sources":["../../src/discovery/PKDetector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAoB,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAmD,MAAM,uBAAuB,CAAC;AACrG,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAMzD,qBAAa,UAAU;IAEnB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,UAAU;gBAFV,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,2BAA2B,EACnC,UAAU,EAAE,gBAAgB;IAGtC;;;;OAIG;IACI,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;IAQ5E,oDAAoD;IAC7C,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;IAI5E;;OAEG;IACU,kBAAkB,CAC7B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,EAAE,CAAC;IAwFzB,kDAAkD;IACrC,kBAAkB,CAC7B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,EAAE,CAAC;IAIzB;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAsC/B;;OAEG;YACW,qBAAqB;IA6InC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAsC5B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA+B9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA+CzB;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;IAkI7B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAoCvB;;;;OAIG;IAEH;;;;;OAKG;IACH,OAAO,CAAC,gCAAgC;YAuC1B,mBAAmB;IA6GjC,sEAAsE;IACtE,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACH,OAAO,CAAC,8BAA8B;IAkCtC,sGAAsG;IACtG,OAAO,CAAC,eAAe;CAWxB"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Analyzes columns to find potential primary keys based on uniqueness,
|
|
4
4
|
* naming patterns, and data characteristics
|
|
5
5
|
*/
|
|
6
|
+
import { ClassifySingleColumnKey, CompositeKeyIndexes, INDEX_KEY_BOOST, UNIQUE_COMPOSITE_CONFIDENCE } from './IndexKeyEvidence.js';
|
|
6
7
|
export class PKDetector {
|
|
7
8
|
constructor(driver, config, statsCache) {
|
|
8
9
|
this.driver = driver;
|
|
@@ -34,7 +35,7 @@ export class PKDetector {
|
|
|
34
35
|
// Single-column PK detection
|
|
35
36
|
for (let colIdx = 0; colIdx < table.columns.length; colIdx++) {
|
|
36
37
|
const column = table.columns[colIdx];
|
|
37
|
-
const candidate = await this.analyzeSingleColumnPK(schemaName, table
|
|
38
|
+
const candidate = await this.analyzeSingleColumnPK(schemaName, table, column, iteration, colIdx);
|
|
38
39
|
if (candidate && candidate.confidence >= this.config.confidence.primaryKeyMinimum * 100) {
|
|
39
40
|
candidates.push(candidate);
|
|
40
41
|
}
|
|
@@ -50,6 +51,14 @@ export class PKDetector {
|
|
|
50
51
|
const compositeCandidates = await this.detectCompositeKeys(schemaName, table, iteration);
|
|
51
52
|
candidates.push(...compositeCandidates);
|
|
52
53
|
}
|
|
54
|
+
// Composite keys backed by a unique index, for tables where no single column qualifies (junction
|
|
55
|
+
// tables, for example). When a single-column candidate already clears primaryKeyMinimum, a composite
|
|
56
|
+
// unique index is an alternate key: adding it as a candidate would get its columns flagged as part of
|
|
57
|
+
// the primary key too, because discovery applies every candidate above the minimum.
|
|
58
|
+
const hasSingleColumnKey = candidates.some(c => c.columnNames.length === 1);
|
|
59
|
+
if (this.useIndexes() && !hasSingleColumnKey) {
|
|
60
|
+
this.mergeCandidates(candidates, this.indexBackedCompositeCandidates(schemaName, table, iteration));
|
|
61
|
+
}
|
|
53
62
|
// H11: Progressive discount for later PK-eligible columns
|
|
54
63
|
// When multiple PK-eligible columns exist, later ones get discounted
|
|
55
64
|
// to reduce false positives from FK columns with high uniqueness
|
|
@@ -130,7 +139,9 @@ export class PKDetector {
|
|
|
130
139
|
/**
|
|
131
140
|
* Analyze a single column as potential PK
|
|
132
141
|
*/
|
|
133
|
-
async analyzeSingleColumnPK(schemaName,
|
|
142
|
+
async analyzeSingleColumnPK(schemaName, table, column, iteration, colOrdinal = 0) {
|
|
143
|
+
const tableName = table.name;
|
|
144
|
+
const indexKey = this.useIndexes() ? ClassifySingleColumnKey(table, column) : undefined;
|
|
134
145
|
// Get or compute column statistics
|
|
135
146
|
let cachedStats = this.statsCache.getColumnStats(schemaName, tableName, column.name);
|
|
136
147
|
if (!cachedStats) {
|
|
@@ -166,12 +177,14 @@ export class PKDetector {
|
|
|
166
177
|
};
|
|
167
178
|
this.statsCache.setColumnStats(cachedStats);
|
|
168
179
|
}
|
|
169
|
-
//
|
|
170
|
-
const
|
|
180
|
+
// A unique index on a NOT NULL column guarantees uniqueness whatever the sample shows (even on an empty table).
|
|
181
|
+
const indexGuaranteesKey = indexKey?.Kind === 'UniqueNotNull';
|
|
182
|
+
const uniqueness = indexGuaranteesKey ? 1.0 : cachedStats.uniqueness;
|
|
171
183
|
// Deterministic eligibility gate — a column is only PK-eligible if it has
|
|
172
184
|
// zero nulls, zero blanks, and 100% unique values. This is the mathematical
|
|
173
185
|
// definition of a primary key and cannot be overridden by the LLM.
|
|
174
|
-
|
|
186
|
+
// A unique index on a NOT NULL column is the same guarantee from the database itself.
|
|
187
|
+
if (!cachedStats.pkEligible && !indexGuaranteesKey) {
|
|
175
188
|
const reasons = [];
|
|
176
189
|
if (cachedStats.nullCount > 0)
|
|
177
190
|
reasons.push(`${cachedStats.nullCount} nulls`);
|
|
@@ -193,6 +206,7 @@ export class PKDetector {
|
|
|
193
206
|
// Use cached data pattern
|
|
194
207
|
const dataPattern = cachedStats.dataPattern;
|
|
195
208
|
// Calculate overall confidence
|
|
209
|
+
const indexBoost = indexKey ? INDEX_KEY_BOOST[indexKey.Kind] : 0;
|
|
196
210
|
const confidence = this.calculatePKConfidence({
|
|
197
211
|
uniqueness,
|
|
198
212
|
nullCount: cachedStats.nullCount,
|
|
@@ -201,7 +215,7 @@ export class PKDetector {
|
|
|
201
215
|
namingScore,
|
|
202
216
|
dataTypeScore,
|
|
203
217
|
warnings: []
|
|
204
|
-
}, tableName, column.name, colOrdinal);
|
|
218
|
+
}, tableName, column.name, colOrdinal, indexBoost);
|
|
205
219
|
// Build evidence
|
|
206
220
|
const evidence = {
|
|
207
221
|
uniqueness,
|
|
@@ -210,8 +224,12 @@ export class PKDetector {
|
|
|
210
224
|
dataPattern,
|
|
211
225
|
namingScore,
|
|
212
226
|
dataTypeScore,
|
|
213
|
-
warnings: []
|
|
227
|
+
warnings: [],
|
|
228
|
+
backingIndex: indexKey?.Index.IndexName
|
|
214
229
|
};
|
|
230
|
+
if (indexKey) {
|
|
231
|
+
evidence.warnings.push(`Backed by ${indexKey.Kind === 'UniqueNotNull' ? 'a unique index on a NOT NULL column' : 'a unique index on a nullable column'}: ${indexKey.Index.IndexName}`);
|
|
232
|
+
}
|
|
215
233
|
// Add warnings (nulls/blanks are already hard-rejected by eligibility gate)
|
|
216
234
|
if (uniqueness < 1.0) {
|
|
217
235
|
evidence.warnings.push(`Only ${(uniqueness * 100).toFixed(1)}% unique values`);
|
|
@@ -347,7 +365,7 @@ export class PKDetector {
|
|
|
347
365
|
* - Composite keys: 50-75
|
|
348
366
|
* - Everything else: <50
|
|
349
367
|
*/
|
|
350
|
-
calculatePKConfidence(evidence, tableName, columnName, colOrdinal = 0) {
|
|
368
|
+
calculatePKConfidence(evidence, tableName, columnName, colOrdinal = 0, indexBoost = 0) {
|
|
351
369
|
let score = 0;
|
|
352
370
|
// **CRITICAL FIX**: Reject obvious non-PK columns immediately
|
|
353
371
|
const blacklistPatterns = [
|
|
@@ -436,6 +454,11 @@ export class PKDetector {
|
|
|
436
454
|
score += boost;
|
|
437
455
|
console.log(`[PKDetector] ${columnName}: surrogate key boost +${boost} (final: ${Math.round(Math.min(score, 100))})`);
|
|
438
456
|
}
|
|
457
|
+
// Existing unique index on the column: strong evidence the database itself enforces (see IndexKeyEvidence)
|
|
458
|
+
if (indexBoost > 0) {
|
|
459
|
+
score += indexBoost;
|
|
460
|
+
console.log(`[PKDetector] ${columnName}: unique index boost +${indexBoost}`);
|
|
461
|
+
}
|
|
439
462
|
// H9: Position-based multiplier — PKs are almost always first columns
|
|
440
463
|
// 100% of correct PKs in AW benchmark start at position 0
|
|
441
464
|
const positionMultipliers = [1.0, 0.85, 0.70, 0.55, 0.40];
|
|
@@ -604,5 +627,60 @@ export class PKDetector {
|
|
|
604
627
|
}
|
|
605
628
|
return candidates;
|
|
606
629
|
}
|
|
630
|
+
/** Whether index-backed candidates are switched on (default true). */
|
|
631
|
+
useIndexes() {
|
|
632
|
+
return this.config.indexBackedCandidates !== false;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Composite candidates from multi-column unique (and primary-key) indexes. A unique index allows repeated NULLs,
|
|
636
|
+
* so every column must be NOT NULL, or nullable with no nulls in a non-empty table.
|
|
637
|
+
*/
|
|
638
|
+
indexBackedCompositeCandidates(schemaName, table, iteration) {
|
|
639
|
+
const candidates = [];
|
|
640
|
+
for (const ix of CompositeKeyIndexes(table)) {
|
|
641
|
+
const columns = ix.Columns.map((name) => table.columns.find((c) => c.name.toLowerCase() === name.toLowerCase()));
|
|
642
|
+
if (columns.some((c) => !c))
|
|
643
|
+
continue;
|
|
644
|
+
const cols = columns;
|
|
645
|
+
const names = cols.map((c) => c.name);
|
|
646
|
+
const stats = cols.map((c) => this.statsCache.getColumnStats(schemaName, table.name, c.name));
|
|
647
|
+
const totalRows = stats.find((s) => s)?.totalRows ?? table.rowCount ?? 0;
|
|
648
|
+
const allNotNull = cols.every((c) => !c.isNullable);
|
|
649
|
+
const noNullsObserved = totalRows > 0 && cols.every((c, i) => !c.isNullable || (stats[i] !== undefined && stats[i].nullCount === 0));
|
|
650
|
+
if (!allNotNull && !noNullsObserved) {
|
|
651
|
+
console.log(`[PKDetector] ${table.name}: unique index ${ix.IndexName} skipped - nullable columns without evidence of no nulls`);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
const confidence = allNotNull ? UNIQUE_COMPOSITE_CONFIDENCE.NotNull : UNIQUE_COMPOSITE_CONFIDENCE.NoNullsObserved;
|
|
655
|
+
console.log(`[PKDetector] ${table.name} [${names.join(', ')}]: composite key backed by unique index ${ix.IndexName} (confidence ${confidence})`);
|
|
656
|
+
candidates.push({
|
|
657
|
+
schemaName,
|
|
658
|
+
tableName: table.name,
|
|
659
|
+
columnNames: names,
|
|
660
|
+
confidence,
|
|
661
|
+
evidence: {
|
|
662
|
+
uniqueness: 1.0, nullCount: 0, totalRows, dataPattern: 'composite', namingScore: 0.8, dataTypeScore: 0.9,
|
|
663
|
+
warnings: [`Backed by unique index ${ix.IndexName}`], backingIndex: ix.IndexName
|
|
664
|
+
},
|
|
665
|
+
discoveredInIteration: iteration,
|
|
666
|
+
validatedByLLM: false,
|
|
667
|
+
status: 'candidate'
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
return candidates;
|
|
671
|
+
}
|
|
672
|
+
/** Adds candidates, keeping the higher-confidence one when the same column set is already present. */
|
|
673
|
+
mergeCandidates(into, extra) {
|
|
674
|
+
const key = (c) => c.columnNames.map((n) => n.toLowerCase()).sort().join('\u0000');
|
|
675
|
+
for (const candidate of extra) {
|
|
676
|
+
const existing = into.findIndex((c) => key(c) === key(candidate));
|
|
677
|
+
if (existing < 0) {
|
|
678
|
+
into.push(candidate);
|
|
679
|
+
}
|
|
680
|
+
else if (candidate.confidence > into[existing].confidence) {
|
|
681
|
+
into[existing] = candidate;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
607
685
|
}
|
|
608
686
|
//# sourceMappingURL=PKDetector.js.map
|