@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
|
@@ -19,8 +19,18 @@ const DEFAULT_CONFIG = {
|
|
|
19
19
|
/Description$/i,
|
|
20
20
|
/Comment$/i,
|
|
21
21
|
/Address/i,
|
|
22
|
+
// MJ's own system columns: their domain is defined by MJ, not by whatever a sample holds.
|
|
23
|
+
/^__mj_/i,
|
|
24
|
+
// Audit columns naming who wrote the row (e.g. CreatedBy, cst_add_user): a list of users, not a closed set.
|
|
25
|
+
/(^|_)(add|change|changed|create|created|modify|modified|update|updated|delete|deleted|insert|inserted)_?(user|usr|by|login)$/i,
|
|
22
26
|
],
|
|
23
27
|
};
|
|
28
|
+
/** Value shapes that make a column a poor closed list even when its cardinality is low. */
|
|
29
|
+
const VALUE_SHAPES_NOT_ENUM = [
|
|
30
|
+
/^(19|20)\d{2}$/, // years
|
|
31
|
+
/^\{?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}?$/i, // GUIDs
|
|
32
|
+
/^\d{4}-\d{2}-\d{2}([ T].*)?$/, // ISO dates and timestamps
|
|
33
|
+
];
|
|
24
34
|
/** Set of data-type base names eligible for enum detection (string types) */
|
|
25
35
|
const STRING_TYPES = new Set([
|
|
26
36
|
'char', 'nchar', 'varchar', 'nvarchar',
|
|
@@ -56,7 +66,7 @@ export class EnumCandidateGate {
|
|
|
56
66
|
EvaluateColumn(column, tableRowCount) {
|
|
57
67
|
if (!this.passesDataTypeGate(column.dataType))
|
|
58
68
|
return null;
|
|
59
|
-
if (!this.passesColumnLengthGate(column
|
|
69
|
+
if (!this.passesColumnLengthGate(column))
|
|
60
70
|
return null;
|
|
61
71
|
if (!this.passesNameExclusionGate(column.name))
|
|
62
72
|
return null;
|
|
@@ -74,6 +84,8 @@ export class EnumCandidateGate {
|
|
|
74
84
|
const values = this.extractValues(stats);
|
|
75
85
|
if (values.length < 2)
|
|
76
86
|
return null;
|
|
87
|
+
if (!this.passesValueShapeGate(values))
|
|
88
|
+
return null;
|
|
77
89
|
return {
|
|
78
90
|
columnName: column.name,
|
|
79
91
|
values,
|
|
@@ -81,9 +93,23 @@ export class EnumCandidateGate {
|
|
|
81
93
|
totalRows: tableRowCount,
|
|
82
94
|
cardinalityRatio: tableRowCount > 0 ? stats.distinctCount / tableRowCount : 1,
|
|
83
95
|
dataType: column.dataType,
|
|
84
|
-
maxLength: this.
|
|
96
|
+
maxLength: this.declaredLength(column) ?? undefined,
|
|
85
97
|
};
|
|
86
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Checks an LLM value-list verdict against the data. The verdict counts only for a column that
|
|
101
|
+
* passes every gate here, and its values are cut down to the ones observed in that column, so a
|
|
102
|
+
* value the model invented never reaches the output. Returns the values to keep, or null when
|
|
103
|
+
* the verdict should be dropped (not a candidate, or fewer than two observed values remain).
|
|
104
|
+
*/
|
|
105
|
+
FilterVerdictValues(column, tableRowCount, verdictValues) {
|
|
106
|
+
const candidate = this.EvaluateColumn(column, tableRowCount);
|
|
107
|
+
if (!candidate)
|
|
108
|
+
return null;
|
|
109
|
+
const observed = new Set(candidate.values);
|
|
110
|
+
const kept = verdictValues.filter(v => observed.has(v));
|
|
111
|
+
return kept.length >= 2 ? kept : null;
|
|
112
|
+
}
|
|
87
113
|
/** @deprecated Use {@link EvaluateColumn}. */
|
|
88
114
|
evaluateColumn(column, tableRowCount) {
|
|
89
115
|
return this.EvaluateColumn(column, tableRowCount);
|
|
@@ -100,10 +126,15 @@ export class EnumCandidateGate {
|
|
|
100
126
|
/**
|
|
101
127
|
* Gate: declared column length must be ≤ maxColumnLength.
|
|
102
128
|
* Unbounded text/varchar(max) is excluded.
|
|
129
|
+
*
|
|
130
|
+
* The drivers report the bare type name (`varchar`, `nvarchar`) with the length in
|
|
131
|
+
* `column.maxLength`, so the length comes from there when the type string carries none.
|
|
132
|
+
* Reading only the type string made every varchar/nvarchar column look unbounded, so no
|
|
133
|
+
* string column was ever a candidate.
|
|
103
134
|
*/
|
|
104
|
-
passesColumnLengthGate(
|
|
105
|
-
const baseType = this.parseBaseType(dataType);
|
|
106
|
-
const length = this.
|
|
135
|
+
passesColumnLengthGate(column) {
|
|
136
|
+
const baseType = this.parseBaseType(column.dataType);
|
|
137
|
+
const length = this.declaredLength(column);
|
|
107
138
|
// Unbounded text type — exclude
|
|
108
139
|
if (baseType === 'text' && length === null)
|
|
109
140
|
return false;
|
|
@@ -135,6 +166,13 @@ export class EnumCandidateGate {
|
|
|
135
166
|
return true;
|
|
136
167
|
return !EXCLUDED_PATTERNS.has(stats.dataPattern);
|
|
137
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Gate: the observed values must not all be years, GUIDs or dates. A year column grows every
|
|
171
|
+
* year, and GUIDs and dates are keys or timestamps, so none of them is a closed set.
|
|
172
|
+
*/
|
|
173
|
+
passesValueShapeGate(values) {
|
|
174
|
+
return !VALUE_SHAPES_NOT_ENUM.some(shape => values.every(v => shape.test(v)));
|
|
175
|
+
}
|
|
138
176
|
/**
|
|
139
177
|
* Gate: distinct count must be ≥ 2 and ≤ maxDistinctValues.
|
|
140
178
|
*/
|
|
@@ -163,6 +201,10 @@ export class EnumCandidateGate {
|
|
|
163
201
|
const base = dataType.replace(/\(.*\)/, '').trim().toLowerCase();
|
|
164
202
|
return base;
|
|
165
203
|
}
|
|
204
|
+
/** The declared length: from the type string when it carries one, else from the driver's maxLength. */
|
|
205
|
+
declaredLength(column) {
|
|
206
|
+
return this.parseDeclaredLength(column.dataType) ?? column.maxLength ?? null;
|
|
207
|
+
}
|
|
166
208
|
/**
|
|
167
209
|
* Parse the declared length from a type string.
|
|
168
210
|
* Returns null for unbounded types or types without length.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnumCandidateGate.js","sourceRoot":"","sources":["../../src/discovery/EnumCandidateGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8BH,MAAM,cAAc,GAAmB;IACrC,eAAe,EAAE,EAAE;IACnB,iBAAiB,EAAE,EAAE;IACrB,YAAY,EAAE,EAAE;IAChB,mBAAmB,EAAE,IAAI;IACzB,yBAAyB,EAAE;QACzB,UAAU;QACV,eAAe;QACf,WAAW;QACX,UAAU;
|
|
1
|
+
{"version":3,"file":"EnumCandidateGate.js","sourceRoot":"","sources":["../../src/discovery/EnumCandidateGate.ts"],"names":[],"mappings":"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"}
|
|
@@ -85,6 +85,28 @@ export interface FKGraphWalkerOptions {
|
|
|
85
85
|
* Default true.
|
|
86
86
|
*/
|
|
87
87
|
PruneCycles?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Hard ceiling on the number of BFS states held at once for a single
|
|
90
|
+
* (spoke → hub) search. See {@link bfsPaths} for why an unbounded frontier
|
|
91
|
+
* is not a theoretical concern.
|
|
92
|
+
*/
|
|
93
|
+
MaxFrontier?: number;
|
|
94
|
+
/** Hard ceiling on the paths retained per (spoke → hub) search. */
|
|
95
|
+
MaxPathsPerPair?: number;
|
|
96
|
+
/** Hard ceiling on the paths retained across the whole walk. */
|
|
97
|
+
MaxTotalPaths?: number;
|
|
98
|
+
}
|
|
99
|
+
/** What a bounded walk found, and whether a bound stopped it finding more. */
|
|
100
|
+
export interface BridgePathWalkResult {
|
|
101
|
+
Paths: BridgePath[];
|
|
102
|
+
/** True when any cap (frontier, per-pair, or total) truncated the search. */
|
|
103
|
+
Truncated: boolean;
|
|
104
|
+
/** Which caps fired, for logging. Empty when the walk ran to completion. */
|
|
105
|
+
TruncationReasons: Array<'frontier' | 'pathsPerPair' | 'totalPaths'>;
|
|
106
|
+
/** (spoke, hub) pairs actually searched, after skipping pairs with no graph edge. */
|
|
107
|
+
PairsSearched: number;
|
|
108
|
+
/** (spoke, hub) pairs skipped because the spoke has no FK edge at all. */
|
|
109
|
+
PairsSkipped: number;
|
|
88
110
|
}
|
|
89
111
|
/**
|
|
90
112
|
* Build the join graph and find all bridge paths from each spoke candidate to
|
|
@@ -105,6 +127,19 @@ export declare function FindBridgePaths(edges: FKEdge[], hubs: Array<{
|
|
|
105
127
|
schema: string;
|
|
106
128
|
table: string;
|
|
107
129
|
}>, opts?: FKGraphWalkerOptions): BridgePath[];
|
|
130
|
+
/**
|
|
131
|
+
* The bounded walk. {@link FindBridgePaths} is the historical shape (paths only);
|
|
132
|
+
* this one also reports whether a bound truncated the search, which is the
|
|
133
|
+
* difference between "this schema has no bridges" and "we stopped looking".
|
|
134
|
+
*/
|
|
135
|
+
export declare function WalkBridgePaths(edges: FKEdge[], hubs: Array<{
|
|
136
|
+
schema: string;
|
|
137
|
+
table: string;
|
|
138
|
+
keyField: string;
|
|
139
|
+
}>, spokes: Array<{
|
|
140
|
+
schema: string;
|
|
141
|
+
table: string;
|
|
142
|
+
}>, opts?: FKGraphWalkerOptions): BridgePathWalkResult;
|
|
108
143
|
/** @deprecated Use {@link FindBridgePaths}. */
|
|
109
144
|
export declare function findBridgePaths(edges: FKEdge[], hubs: Array<{
|
|
110
145
|
schema: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FKGraphWalker.d.ts","sourceRoot":"","sources":["../../src/discovery/FKGraphWalker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,uDAAuD;AACvD,MAAM,WAAW,MAAM;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACvB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACjC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FKGraphWalker.d.ts","sourceRoot":"","sources":["../../src/discovery/FKGraphWalker.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,13 @@ const DEFAULTS = {
|
|
|
27
27
|
MaxHops: 3,
|
|
28
28
|
MinSoftFKConfidence: 0.6,
|
|
29
29
|
PruneCycles: true,
|
|
30
|
+
// THE NUMBERS ARE CEILINGS, NOT TUNING. They exist so that a graph nobody
|
|
31
|
+
// anticipated cannot take the process down; a schema that hits one is a
|
|
32
|
+
// schema whose bridge set was never going to be usable anyway, and the
|
|
33
|
+
// truncation is reported rather than swallowed.
|
|
34
|
+
MaxFrontier: 50_000,
|
|
35
|
+
MaxPathsPerPair: 50,
|
|
36
|
+
MaxTotalPaths: 25_000,
|
|
30
37
|
};
|
|
31
38
|
/**
|
|
32
39
|
* Build the join graph and find all bridge paths from each spoke candidate to
|
|
@@ -40,23 +47,69 @@ const DEFAULTS = {
|
|
|
40
47
|
* table in the database except the hub itself.
|
|
41
48
|
*/
|
|
42
49
|
export function FindBridgePaths(edges, hubs, spokes, opts = {}) {
|
|
43
|
-
|
|
50
|
+
return WalkBridgePaths(edges, hubs, spokes, opts).Paths;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The bounded walk. {@link FindBridgePaths} is the historical shape (paths only);
|
|
54
|
+
* this one also reports whether a bound truncated the search, which is the
|
|
55
|
+
* difference between "this schema has no bridges" and "we stopped looking".
|
|
56
|
+
*/
|
|
57
|
+
export function WalkBridgePaths(edges, hubs, spokes, opts = {}) {
|
|
58
|
+
// `{ ...DEFAULTS, ...opts }` would let an explicitly-undefined option UNSET a ceiling, which
|
|
59
|
+
// is the one way a caller could accidentally un-bound the walk. Only defined keys override.
|
|
60
|
+
const o = { ...DEFAULTS };
|
|
61
|
+
for (const key of Object.keys(DEFAULTS)) {
|
|
62
|
+
const supplied = opts[key];
|
|
63
|
+
if (supplied !== undefined) {
|
|
64
|
+
o[key] = supplied;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
44
67
|
// Build the adjacency map keyed by "schema.table".
|
|
45
68
|
const adjacency = buildAdjacency(edges, o.MinSoftFKConfidence);
|
|
69
|
+
const reasons = new Set();
|
|
46
70
|
const out = [];
|
|
47
|
-
|
|
71
|
+
let pairsSearched = 0;
|
|
72
|
+
let pairsSkipped = 0;
|
|
73
|
+
// GATE ON THE GRAPH, NOT ON THE INPUTS. An empty adjacency means no table is
|
|
74
|
+
// joined to any other, so every BFS below would dequeue its start node, find
|
|
75
|
+
// no neighbours and return nothing — `hubs × spokes` times.
|
|
76
|
+
if (adjacency.size === 0) {
|
|
77
|
+
return { Paths: [], Truncated: false, TruncationReasons: [], PairsSearched: 0, PairsSkipped: hubs.length * spokes.length };
|
|
78
|
+
}
|
|
79
|
+
outer: for (const hub of hubs) {
|
|
48
80
|
const hubKey = `${hub.schema}.${hub.table}`;
|
|
81
|
+
// A hub with no edge of its own is unreachable from every spoke.
|
|
82
|
+
if (!adjacency.has(hubKey)) {
|
|
83
|
+
pairsSkipped += spokes.length;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
49
86
|
for (const spoke of spokes) {
|
|
50
87
|
const spokeKey = `${spoke.schema}.${spoke.table}`;
|
|
51
88
|
if (spokeKey === hubKey)
|
|
52
89
|
continue;
|
|
90
|
+
// A spoke with no edge of its own cannot reach anything. Skipping it here is
|
|
91
|
+
// what turns "one BFS per hub × EVERY TABLE IN THE DATABASE" back into one
|
|
92
|
+
// BFS per hub × every table that is actually joined to something.
|
|
93
|
+
if (!adjacency.has(spokeKey)) {
|
|
94
|
+
pairsSkipped++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
pairsSearched++;
|
|
53
98
|
// BFS from spoke → hub.
|
|
54
|
-
const
|
|
55
|
-
|
|
99
|
+
const search = bfsPaths(adjacency, spokeKey, hubKey, o.MaxHops, o.PruneCycles, o.MaxFrontier, o.MaxPathsPerPair);
|
|
100
|
+
if (search.frontierTruncated)
|
|
101
|
+
reasons.add('frontier');
|
|
102
|
+
if (search.pathsTruncated)
|
|
103
|
+
reasons.add('pathsPerPair');
|
|
104
|
+
for (const p of search.paths) {
|
|
56
105
|
if (p.length === 0)
|
|
57
106
|
continue; // self
|
|
58
107
|
if (p.length === 1)
|
|
59
108
|
continue; // direct FK already handled by existing relationship system
|
|
109
|
+
if (out.length >= o.MaxTotalPaths) {
|
|
110
|
+
reasons.add('totalPaths');
|
|
111
|
+
break outer;
|
|
112
|
+
}
|
|
60
113
|
out.push(materializeBridgePath(p, hub, spoke));
|
|
61
114
|
}
|
|
62
115
|
}
|
|
@@ -67,7 +120,13 @@ export function FindBridgePaths(edges, hubs, spokes, opts = {}) {
|
|
|
67
120
|
return a.PathLength - b.PathLength;
|
|
68
121
|
return b.PathConfidence - a.PathConfidence;
|
|
69
122
|
});
|
|
70
|
-
return
|
|
123
|
+
return {
|
|
124
|
+
Paths: out,
|
|
125
|
+
Truncated: reasons.size > 0,
|
|
126
|
+
TruncationReasons: [...reasons],
|
|
127
|
+
PairsSearched: pairsSearched,
|
|
128
|
+
PairsSkipped: pairsSkipped,
|
|
129
|
+
};
|
|
71
130
|
}
|
|
72
131
|
/** @deprecated Use {@link FindBridgePaths}. */
|
|
73
132
|
export function findBridgePaths(edges, hubs, spokes, opts = {}) {
|
|
@@ -110,17 +169,46 @@ function push(map, key, value) {
|
|
|
110
169
|
else
|
|
111
170
|
map.set(key, [value]);
|
|
112
171
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Path of adjacency edges from `start` to `goal`, bounded in both frontier size and
|
|
174
|
+
* result count. Returns empty paths if none found.
|
|
175
|
+
*
|
|
176
|
+
* WHY THE BOUNDS AND THE CURSOR ARE NOT MICRO-OPTIMISATION. Three properties compounded
|
|
177
|
+
* into an out-of-memory kill on a large schema before the phase could emit anything:
|
|
178
|
+
*
|
|
179
|
+
* 1. Every enqueued state carried a FRESH `Set<string>` and a FRESH path array, allocated
|
|
180
|
+
* per neighbour per dequeued node. On a dense graph with `maxHops` 3 that is
|
|
181
|
+
* O(branching³) live Sets, each holding up to `maxHops + 1` strings.
|
|
182
|
+
* 2. The queue was drained with `Array.shift()`, which is O(n) on a large array, so
|
|
183
|
+
* draining it was quadratic on top of the allocation.
|
|
184
|
+
* 3. Nothing bounded either the queue or the result array.
|
|
185
|
+
*
|
|
186
|
+
* The cursor fixes (2) outright. The caps fix (1) and (3) by refusing to grow past a
|
|
187
|
+
* ceiling and SAYING SO, rather than by silently returning a partial answer. The visited
|
|
188
|
+
* Set is still copied per state — a path-specific visited set is what `pruneCycles` means,
|
|
189
|
+
* and a shared one would wrongly prune valid alternate paths — but it can no longer be
|
|
190
|
+
* copied an unbounded number of times.
|
|
191
|
+
*/
|
|
192
|
+
function bfsPaths(adjacency, start, goal, maxHops, pruneCycles, maxFrontier, maxPaths) {
|
|
116
193
|
if (start === goal)
|
|
117
|
-
return [[]];
|
|
194
|
+
return { paths: [[]], frontierTruncated: false, pathsTruncated: false };
|
|
118
195
|
const queue = [
|
|
119
196
|
{ node: start, pathEdges: [], visited: new Set([start]) },
|
|
120
197
|
];
|
|
198
|
+
// Index cursor instead of Array.shift(): shift() is O(n) on a large array, which made
|
|
199
|
+
// draining the queue quadratic in the number of enqueued states.
|
|
200
|
+
let head = 0;
|
|
121
201
|
const found = [];
|
|
122
|
-
|
|
123
|
-
|
|
202
|
+
let frontierTruncated = false;
|
|
203
|
+
let pathsTruncated = false;
|
|
204
|
+
while (head < queue.length) {
|
|
205
|
+
const { node, pathEdges, visited } = queue[head];
|
|
206
|
+
// Release the slot as we pass it. An index cursor alone keeps every state ever enqueued
|
|
207
|
+
// reachable from the array — including its visited Set — which would trade Array.shift()'s
|
|
208
|
+
// O(n) drain for a retained-memory leak over a long walk. Nulling gives O(1) dequeue AND
|
|
209
|
+
// lets the frontier bound below actually bound live memory.
|
|
210
|
+
queue[head] = undefined;
|
|
211
|
+
head++;
|
|
124
212
|
if (pathEdges.length >= maxHops)
|
|
125
213
|
continue;
|
|
126
214
|
const neighbors = adjacency.get(node) ?? [];
|
|
@@ -130,15 +218,28 @@ function bfsPaths(adjacency, start, goal, maxHops, pruneCycles) {
|
|
|
130
218
|
continue;
|
|
131
219
|
const newPath = [...pathEdges, edge];
|
|
132
220
|
if (nextNode === goal) {
|
|
221
|
+
if (found.length >= maxPaths) {
|
|
222
|
+
pathsTruncated = true;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
133
225
|
found.push(newPath);
|
|
134
226
|
continue; // don't extend past the goal
|
|
135
227
|
}
|
|
228
|
+
// The live frontier is what is enqueued but not yet expanded. Bounding THAT
|
|
229
|
+
// rather than total enqueues keeps a long, narrow walk working while still
|
|
230
|
+
// refusing to hold an unbounded number of visited-set copies at once.
|
|
231
|
+
if (queue.length - head >= maxFrontier) {
|
|
232
|
+
frontierTruncated = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
136
235
|
const nextVisited = new Set(visited);
|
|
137
236
|
nextVisited.add(nextNode);
|
|
138
237
|
queue.push({ node: nextNode, pathEdges: newPath, visited: nextVisited });
|
|
139
238
|
}
|
|
239
|
+
if (pathsTruncated)
|
|
240
|
+
break;
|
|
140
241
|
}
|
|
141
|
-
return found;
|
|
242
|
+
return { paths: found, frontierTruncated, pathsTruncated };
|
|
142
243
|
}
|
|
143
244
|
// ─── Bridge path materialization ────────────────────────────────────────────
|
|
144
245
|
function materializeBridgePath(edgePath, hub, spoke) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FKGraphWalker.js","sourceRoot":"","sources":["../../src/discovery/FKGraphWalker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;
|
|
1
|
+
{"version":3,"file":"FKGraphWalker.js","sourceRoot":"","sources":["../../src/discovery/FKGraphWalker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA+EH,MAAM,QAAQ,GAAmC;IAC7C,OAAO,EAAE,CAAC;IACV,mBAAmB,EAAE,GAAG;IACxB,WAAW,EAAE,IAAI;IACjB,0EAA0E;IAC1E,wEAAwE;IACxE,uEAAuE;IACvE,gDAAgD;IAChD,WAAW,EAAE,MAAM;IACnB,eAAe,EAAE,EAAE;IACnB,aAAa,EAAE,MAAM;CACxB,CAAC;AAeF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAC3B,KAAe,EACf,IAAgE,EAChE,MAAgD,EAChD,OAA6B,EAAE;IAE/B,OAAO,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC3B,KAAe,EACf,IAAgE,EAChE,MAAgD,EAChD,OAA6B,EAAE;IAE/B,6FAA6F;IAC7F,4FAA4F;IAC5F,MAAM,CAAC,GAAmC,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC1D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAsC,EAAE,CAAC;QAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACxB,CAAC,CAAC,GAAG,CAAqB,GAAG,QAAQ,CAAC;QAC3C,CAAC;IACL,CAAC;IAED,mDAAmD;IACnD,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,mBAAmB,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8C,CAAC;IACtE,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,6EAA6E;IAC7E,6EAA6E;IAC7E,4DAA4D;IAC5D,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;IAC/H,CAAC;IAED,KAAK,EACL,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAC5C,iEAAiE;QACjE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,YAAY,IAAI,MAAM,CAAC,MAAM,CAAC;YAC9B,SAAS;QACb,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAClD,IAAI,QAAQ,KAAK,MAAM;gBAAE,SAAS;YAClC,6EAA6E;YAC7E,2EAA2E;YAC3E,kEAAkE;YAClE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC3B,YAAY,EAAE,CAAC;gBACf,SAAS;YACb,CAAC;YACD,aAAa,EAAE,CAAC;YAChB,wBAAwB;YACxB,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC;YACjH,IAAI,MAAM,CAAC,iBAAiB;gBAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,cAAc;gBAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YACvD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBAC3B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS,CAAC,OAAO;gBACrC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS,CAAC,4DAA4D;gBAC1F,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;oBAChC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;oBAC1B,MAAM,KAAK,CAAC;gBAChB,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YACnD,CAAC;QACL,CAAC;IACL,CAAC;IACD,uDAAuD;IACvD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACd,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU;YAAE,OAAO,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;QACtE,OAAO,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC;IAC/C,CAAC,CAAC,CAAC;IACH,OAAO;QACH,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,OAAO,CAAC,IAAI,GAAG,CAAC;QAC3B,iBAAiB,EAAE,CAAC,GAAG,OAAO,CAAC;QAC/B,aAAa,EAAE,aAAa;QAC5B,YAAY,EAAE,YAAY;KAC7B,CAAC;AACN,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,eAAe,CAC3B,KAAe,EACf,IAAgE,EAChE,MAAgD,EAChD,OAA6B,EAAE;IAE/B,OAAO,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC;AAgBD,SAAS,cAAc,CAAC,KAAe,EAAE,mBAA2B;IAChE,MAAM,GAAG,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,GAAG,mBAAmB;YAAE,SAAS;QACtE,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,OAAO,GAAkB;YAC3B,OAAO,EAAE,IAAI;YACb,KAAK,EAAE,IAAI;YACX,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE,CAAC,CAAC,YAAY;YAC9B,cAAc,EAAE,CAAC,CAAC,YAAY;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,UAAU,EAAE,CAAC,CAAC,UAAU;SAC3B,CAAC;QACF,MAAM,OAAO,GAAkB;YAC3B,OAAO,EAAE,IAAI;YACb,KAAK,EAAE,IAAI;YACX,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE,CAAC,CAAC,YAAY;YAC9B,cAAc,EAAE,CAAC,CAAC,YAAY;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,UAAU,EAAE,CAAC,CAAC,UAAU;SAC3B,CAAC;QACF,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACzB,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAS,IAAI,CAAO,GAAgB,EAAE,GAAM,EAAE,KAAQ;IAClD,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;QACtB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/B,CAAC;AAaD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAS,QAAQ,CACb,SAAuC,EACvC,KAAa,EACb,IAAY,EACZ,OAAe,EACf,WAAoB,EACpB,WAAmB,EACnB,QAAgB;IAEhB,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,iBAAiB,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IAC5F,MAAM,KAAK,GAAuF;QAC9F,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE;KAC5D,CAAC;IACF,sFAAsF;IACtF,iEAAiE;IACjE,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAC9B,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAE,CAAC;QAClD,wFAAwF;QACxF,2FAA2F;QAC3F,yFAAyF;QACzF,4DAA4D;QAC5D,KAAK,CAAC,IAAI,CAAC,GAAG,SAA8C,CAAC;QAC7D,IAAI,EAAE,CAAC;QACP,IAAI,SAAS,CAAC,MAAM,IAAI,OAAO;YAAE,SAAS;QAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;YAC5B,IAAI,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACnD,MAAM,OAAO,GAAG,CAAC,GAAG,SAAS,EAAE,IAAI,CAAC,CAAC;YACrC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACpB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;oBAC3B,cAAc,GAAG,IAAI,CAAC;oBACtB,MAAM;gBACV,CAAC;gBACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpB,SAAS,CAAC,6BAA6B;YAC3C,CAAC;YACD,4EAA4E;YAC5E,2EAA2E;YAC3E,sEAAsE;YACtE,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,IAAI,WAAW,EAAE,CAAC;gBACrC,iBAAiB,GAAG,IAAI,CAAC;gBACzB,MAAM;YACV,CAAC;YACD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;YACrC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,cAAc;YAAE,MAAM;IAC9B,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AAC/D,CAAC;AAED,+EAA+E;AAE/E,SAAS,qBAAqB,CAC1B,QAAyB,EACzB,GAAwD,EACxD,KAAwC;IAExC,MAAM,IAAI,GAAgB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACzC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/C,kFAAkF;QAClF,uCAAuC;QACvC,MAAM,cAAc,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC;QACnD,OAAO;YACH,UAAU;YACV,SAAS;YACT,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc;YAChE,QAAQ;YACR,OAAO;YACP,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc;YAC9D,IAAI,EAAE,CAAC,CAAC,IAAI;SACf,CAAC;IACN,CAAC,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC1E,OAAO;QACH,WAAW,EAAE,KAAK,CAAC,MAAM;QACzB,UAAU,EAAE,KAAK,CAAC,KAAK;QACvB,SAAS,EAAE,GAAG,CAAC,MAAM;QACrB,QAAQ,EAAE,GAAG,CAAC,KAAK;QACnB,WAAW,EAAE,GAAG,CAAC,QAAQ;QACzB,IAAI,EAAE,IAAI;QACV,UAAU,EAAE,QAAQ,CAAC,MAAM;QAC3B,cAAc,EAAE,cAAc;KACjC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primary-key evidence from a table's existing indexes (`TableDefinition.indexes`).
|
|
3
|
+
*
|
|
4
|
+
* A unique index is a guarantee from the database, not a statistic: its key values are distinct
|
|
5
|
+
* whatever the data looks like, including on an empty table. A primary-key index counts as a unique
|
|
6
|
+
* index here: tables with a declared primary key never reach discovery's PK detection (their columns are
|
|
7
|
+
* already flagged isPrimaryKey from the same catalog). PKDetector uses the evidence as follows:
|
|
8
|
+
* - a unique, unfiltered index on NOT NULL columns skips the statistical eligibility gate and is
|
|
9
|
+
* scored with a large index boost (naming, type, blacklist and position rules still apply, so a
|
|
10
|
+
* surrogate `id` still beats a natural-key unique index such as `email`);
|
|
11
|
+
* - a unique index on a nullable column only adds a smaller boost to a column that already passes the
|
|
12
|
+
* statistical gate (non-empty, zero nulls), because NULLs can repeat in a unique index.
|
|
13
|
+
*/
|
|
14
|
+
import type { AutoDocIndex } from '../types/driver.js';
|
|
15
|
+
import type { ColumnDefinition, TableDefinition } from '../types/state.js';
|
|
16
|
+
/** How strongly the indexes back a key. */
|
|
17
|
+
export type IndexKeyKind = 'UniqueNotNull' | 'UniqueNullable';
|
|
18
|
+
/** Index boost added to the confidence score for each kind. */
|
|
19
|
+
export declare const INDEX_KEY_BOOST: Record<IndexKeyKind, number>;
|
|
20
|
+
/** Confidence for a composite key backed by a unique index: NOT NULL columns, or nullable columns with no nulls. */
|
|
21
|
+
export declare const UNIQUE_COMPOSITE_CONFIDENCE: {
|
|
22
|
+
NotNull: number;
|
|
23
|
+
NoNullsObserved: number;
|
|
24
|
+
};
|
|
25
|
+
/** An index that can back a key: primary key or unique, with no filter predicate and no expression parts. */
|
|
26
|
+
export declare function IsKeyIndex(ix: AutoDocIndex): boolean;
|
|
27
|
+
/** The table's key indexes whose key is exactly these columns, in any order (case-insensitive). */
|
|
28
|
+
export declare function KeyIndexesOn(table: Pick<TableDefinition, 'indexes'>, columns: string[]): AutoDocIndex[];
|
|
29
|
+
/** How the table's indexes back a single column as a key, or undefined when they don't. */
|
|
30
|
+
export declare function ClassifySingleColumnKey(table: Pick<TableDefinition, 'indexes'>, column: Pick<ColumnDefinition, 'name' | 'isNullable'>): {
|
|
31
|
+
Kind: IndexKeyKind;
|
|
32
|
+
Index: AutoDocIndex;
|
|
33
|
+
} | undefined;
|
|
34
|
+
/** Multi-column key indexes on the table. */
|
|
35
|
+
export declare function CompositeKeyIndexes(table: Pick<TableDefinition, 'indexes'>): AutoDocIndex[];
|
|
36
|
+
//# sourceMappingURL=IndexKeyEvidence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexKeyEvidence.d.ts","sourceRoot":"","sources":["../../src/discovery/IndexKeyEvidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE3E,2CAA2C;AAC3C,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAE9D,+DAA+D;AAC/D,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAGxD,CAAC;AAEF,oHAAoH;AACpH,eAAO,MAAM,2BAA2B;;;CAAuC,CAAC;AAEhF,6GAA6G;AAC7G,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,OAAO,CAEpD;AAED,mGAAmG;AACnG,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAKvG;AAED,2FAA2F;AAC3F,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,EACvC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,YAAY,CAAC,GACpD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAAG,SAAS,CAMzD;AAED,6CAA6C;AAC7C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,YAAY,EAAE,CAE3F"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primary-key evidence from a table's existing indexes (`TableDefinition.indexes`).
|
|
3
|
+
*
|
|
4
|
+
* A unique index is a guarantee from the database, not a statistic: its key values are distinct
|
|
5
|
+
* whatever the data looks like, including on an empty table. A primary-key index counts as a unique
|
|
6
|
+
* index here: tables with a declared primary key never reach discovery's PK detection (their columns are
|
|
7
|
+
* already flagged isPrimaryKey from the same catalog). PKDetector uses the evidence as follows:
|
|
8
|
+
* - a unique, unfiltered index on NOT NULL columns skips the statistical eligibility gate and is
|
|
9
|
+
* scored with a large index boost (naming, type, blacklist and position rules still apply, so a
|
|
10
|
+
* surrogate `id` still beats a natural-key unique index such as `email`);
|
|
11
|
+
* - a unique index on a nullable column only adds a smaller boost to a column that already passes the
|
|
12
|
+
* statistical gate (non-empty, zero nulls), because NULLs can repeat in a unique index.
|
|
13
|
+
*/
|
|
14
|
+
/** Index boost added to the confidence score for each kind. */
|
|
15
|
+
export const INDEX_KEY_BOOST = {
|
|
16
|
+
UniqueNotNull: 35,
|
|
17
|
+
UniqueNullable: 15
|
|
18
|
+
};
|
|
19
|
+
/** Confidence for a composite key backed by a unique index: NOT NULL columns, or nullable columns with no nulls. */
|
|
20
|
+
export const UNIQUE_COMPOSITE_CONFIDENCE = { NotNull: 85, NoNullsObserved: 75 };
|
|
21
|
+
/** An index that can back a key: primary key or unique, with no filter predicate and no expression parts. */
|
|
22
|
+
export function IsKeyIndex(ix) {
|
|
23
|
+
return (ix.IsPrimaryKey || ix.IsUnique) && !ix.FilterDefinition && !ix.HasExpressions && ix.Columns.length > 0;
|
|
24
|
+
}
|
|
25
|
+
/** The table's key indexes whose key is exactly these columns, in any order (case-insensitive). */
|
|
26
|
+
export function KeyIndexesOn(table, columns) {
|
|
27
|
+
const wanted = new Set(columns.map((c) => c.toLowerCase()));
|
|
28
|
+
return (table.indexes ?? []).filter((ix) => IsKeyIndex(ix) && ix.Columns.length === wanted.size && ix.Columns.every((c) => wanted.has(c.toLowerCase())));
|
|
29
|
+
}
|
|
30
|
+
/** How the table's indexes back a single column as a key, or undefined when they don't. */
|
|
31
|
+
export function ClassifySingleColumnKey(table, column) {
|
|
32
|
+
const unique = KeyIndexesOn(table, [column.name])[0];
|
|
33
|
+
if (!unique) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return { Kind: column.isNullable ? 'UniqueNullable' : 'UniqueNotNull', Index: unique };
|
|
37
|
+
}
|
|
38
|
+
/** Multi-column key indexes on the table. */
|
|
39
|
+
export function CompositeKeyIndexes(table) {
|
|
40
|
+
return (table.indexes ?? []).filter((ix) => IsKeyIndex(ix) && ix.Columns.length > 1);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=IndexKeyEvidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexKeyEvidence.js","sourceRoot":"","sources":["../../src/discovery/IndexKeyEvidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAiC;IAC3D,aAAa,EAAE,EAAE;IACjB,cAAc,EAAE,EAAE;CACnB,CAAC;AAEF,oHAAoH;AACpH,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;AAEhF,6GAA6G;AAC7G,MAAM,UAAU,UAAU,CAAC,EAAgB;IACzC,OAAO,CAAC,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,IAAI,CAAC,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;AACjH,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,YAAY,CAAC,KAAuC,EAAE,OAAiB;IACrF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CACjC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CACpH,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,uBAAuB,CACrC,KAAuC,EACvC,MAAqD;IAErD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACzF,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,mBAAmB,CAAC,KAAuC;IACzE,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACvF,CAAC"}
|