@memberjunction/db-auto-doc 2.117.0 → 2.118.0
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 +652 -165
- package/bin/run.js +7 -0
- package/dist/api/DBAutoDocAPI.d.ts +252 -0
- package/dist/api/DBAutoDocAPI.d.ts.map +1 -0
- package/dist/api/DBAutoDocAPI.js +530 -0
- package/dist/api/DBAutoDocAPI.js.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +10 -0
- package/dist/api/index.js.map +1 -0
- package/dist/commands/analyze.d.ts +6 -4
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +58 -71
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/export.d.ts +14 -4
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +156 -61
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/init.d.ts +3 -4
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +155 -146
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/reset.d.ts +4 -1
- package/dist/commands/reset.d.ts.map +1 -1
- package/dist/commands/reset.js +33 -19
- package/dist/commands/reset.js.map +1 -1
- package/dist/commands/status.d.ts +10 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +66 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/core/AnalysisEngine.d.ts +108 -0
- package/dist/core/AnalysisEngine.d.ts.map +1 -0
- package/dist/core/AnalysisEngine.js +716 -0
- package/dist/core/AnalysisEngine.js.map +1 -0
- package/dist/core/AnalysisOrchestrator.d.ts +37 -0
- package/dist/core/AnalysisOrchestrator.d.ts.map +1 -0
- package/dist/core/AnalysisOrchestrator.js +294 -0
- package/dist/core/AnalysisOrchestrator.js.map +1 -0
- package/dist/core/BackpropagationEngine.d.ts +32 -0
- package/dist/core/BackpropagationEngine.d.ts.map +1 -0
- package/dist/core/BackpropagationEngine.js +121 -0
- package/dist/core/BackpropagationEngine.js.map +1 -0
- package/dist/core/ConvergenceDetector.d.ts +27 -0
- package/dist/core/ConvergenceDetector.d.ts.map +1 -0
- package/dist/core/ConvergenceDetector.js +92 -0
- package/dist/core/ConvergenceDetector.js.map +1 -0
- package/dist/core/GuardrailsManager.d.ts +78 -0
- package/dist/core/GuardrailsManager.d.ts.map +1 -0
- package/dist/core/GuardrailsManager.js +367 -0
- package/dist/core/GuardrailsManager.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -0
- package/dist/database/Database.d.ts +56 -0
- package/dist/database/Database.d.ts.map +1 -0
- package/dist/database/Database.js +172 -0
- package/dist/database/Database.js.map +1 -0
- package/dist/database/TopologicalSorter.d.ts +25 -0
- package/dist/database/TopologicalSorter.d.ts.map +1 -0
- package/dist/database/TopologicalSorter.js +150 -0
- package/dist/database/TopologicalSorter.js.map +1 -0
- package/dist/database/index.d.ts +6 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +14 -0
- package/dist/database/index.js.map +1 -0
- package/dist/discovery/ColumnStatsCache.d.ts +91 -0
- package/dist/discovery/ColumnStatsCache.d.ts.map +1 -0
- package/dist/discovery/ColumnStatsCache.js +231 -0
- package/dist/discovery/ColumnStatsCache.js.map +1 -0
- package/dist/discovery/DiscoveryEngine.d.ts +100 -0
- package/dist/discovery/DiscoveryEngine.d.ts.map +1 -0
- package/dist/discovery/DiscoveryEngine.js +726 -0
- package/dist/discovery/DiscoveryEngine.js.map +1 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.d.ts +57 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.d.ts.map +1 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.js +186 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.js.map +1 -0
- package/dist/discovery/FKDetector.d.ts +47 -0
- package/dist/discovery/FKDetector.d.ts.map +1 -0
- package/dist/discovery/FKDetector.js +317 -0
- package/dist/discovery/FKDetector.js.map +1 -0
- package/dist/discovery/LLMDiscoveryValidator.d.ts +64 -0
- package/dist/discovery/LLMDiscoveryValidator.d.ts.map +1 -0
- package/dist/discovery/LLMDiscoveryValidator.js +431 -0
- package/dist/discovery/LLMDiscoveryValidator.js.map +1 -0
- package/dist/discovery/LLMSanityChecker.d.ts +38 -0
- package/dist/discovery/LLMSanityChecker.d.ts.map +1 -0
- package/dist/discovery/LLMSanityChecker.js +156 -0
- package/dist/discovery/LLMSanityChecker.js.map +1 -0
- package/dist/discovery/PKDetector.d.ts +62 -0
- package/dist/discovery/PKDetector.d.ts.map +1 -0
- package/dist/discovery/PKDetector.js +436 -0
- package/dist/discovery/PKDetector.js.map +1 -0
- package/dist/discovery/index.d.ts +9 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +25 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts +132 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -0
- package/dist/drivers/BaseAutoDocDriver.js +121 -0
- package/dist/drivers/BaseAutoDocDriver.js.map +1 -0
- package/dist/drivers/MySQLDriver.d.ts +61 -0
- package/dist/drivers/MySQLDriver.d.ts.map +1 -0
- package/dist/drivers/MySQLDriver.js +668 -0
- package/dist/drivers/MySQLDriver.js.map +1 -0
- package/dist/drivers/PostgreSQLDriver.d.ts +65 -0
- package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -0
- package/dist/drivers/PostgreSQLDriver.js +704 -0
- package/dist/drivers/PostgreSQLDriver.js.map +1 -0
- package/dist/drivers/SQLServerDriver.d.ts +61 -0
- package/dist/drivers/SQLServerDriver.d.ts.map +1 -0
- package/dist/drivers/SQLServerDriver.js +667 -0
- package/dist/drivers/SQLServerDriver.js.map +1 -0
- package/dist/generators/CSVGenerator.d.ts +35 -0
- package/dist/generators/CSVGenerator.d.ts.map +1 -0
- package/dist/generators/CSVGenerator.js +154 -0
- package/dist/generators/CSVGenerator.js.map +1 -0
- package/dist/generators/HTMLGenerator.d.ts +29 -0
- package/dist/generators/HTMLGenerator.d.ts.map +1 -0
- package/dist/generators/HTMLGenerator.js +710 -0
- package/dist/generators/HTMLGenerator.js.map +1 -0
- package/dist/generators/MarkdownGenerator.d.ts +27 -0
- package/dist/generators/MarkdownGenerator.d.ts.map +1 -0
- package/dist/generators/MarkdownGenerator.js +361 -0
- package/dist/generators/MarkdownGenerator.js.map +1 -0
- package/dist/generators/MermaidGenerator.d.ts +35 -0
- package/dist/generators/MermaidGenerator.d.ts.map +1 -0
- package/dist/generators/MermaidGenerator.js +321 -0
- package/dist/generators/MermaidGenerator.js.map +1 -0
- package/dist/generators/ReportGenerator.d.ts +22 -0
- package/dist/generators/ReportGenerator.d.ts.map +1 -0
- package/dist/generators/ReportGenerator.js +176 -0
- package/dist/generators/ReportGenerator.js.map +1 -0
- package/dist/generators/SQLGenerator.d.ts +31 -0
- package/dist/generators/SQLGenerator.d.ts.map +1 -0
- package/dist/generators/SQLGenerator.js +168 -0
- package/dist/generators/SQLGenerator.js.map +1 -0
- package/dist/generators/index.d.ts +10 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +19 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/index.d.ts +11 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -20
- package/dist/index.js.map +1 -1
- package/dist/prompts/PromptEngine.d.ts +65 -0
- package/dist/prompts/PromptEngine.d.ts.map +1 -0
- package/dist/prompts/PromptEngine.js +282 -0
- package/dist/prompts/PromptEngine.js.map +1 -0
- package/dist/prompts/PromptFileLoader.d.ts +21 -0
- package/dist/prompts/PromptFileLoader.d.ts.map +1 -0
- package/dist/prompts/PromptFileLoader.js +74 -0
- package/dist/prompts/PromptFileLoader.js.map +1 -0
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +11 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/state/IterationTracker.d.ts +64 -0
- package/dist/state/IterationTracker.d.ts.map +1 -0
- package/dist/state/IterationTracker.js +136 -0
- package/dist/state/IterationTracker.js.map +1 -0
- package/dist/state/StateManager.d.ts +79 -0
- package/dist/state/StateManager.d.ts.map +1 -0
- package/dist/state/StateManager.js +348 -0
- package/dist/state/StateManager.js.map +1 -0
- package/dist/state/StateValidator.d.ts +24 -0
- package/dist/state/StateValidator.d.ts.map +1 -0
- package/dist/state/StateValidator.js +147 -0
- package/dist/state/StateValidator.js.map +1 -0
- package/dist/state/index.d.ts +7 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +13 -0
- package/dist/state/index.js.map +1 -0
- package/dist/types/analysis.d.ts +76 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +6 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/config.d.ts +132 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/discovery.d.ts +277 -0
- package/dist/types/discovery.d.ts.map +1 -0
- package/dist/types/discovery.js +7 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/driver.d.ts +148 -0
- package/dist/types/driver.d.ts.map +1 -0
- package/dist/types/driver.js +7 -0
- package/dist/types/driver.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +24 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/prompts.d.ts +158 -0
- package/dist/types/prompts.d.ts.map +1 -0
- package/dist/types/prompts.js +6 -0
- package/dist/types/prompts.js.map +1 -0
- package/dist/types/state.d.ts +278 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +7 -0
- package/dist/types/state.js.map +1 -0
- package/dist/utils/config-loader.d.ts +29 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +163 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +24 -3
- package/dist/ai/simple-ai-client.d.ts +0 -70
- package/dist/ai/simple-ai-client.d.ts.map +0 -1
- package/dist/ai/simple-ai-client.js +0 -181
- package/dist/ai/simple-ai-client.js.map +0 -1
- package/dist/analyzers/analyzer.d.ts +0 -23
- package/dist/analyzers/analyzer.d.ts.map +0 -1
- package/dist/analyzers/analyzer.js +0 -127
- package/dist/analyzers/analyzer.js.map +0 -1
- package/dist/cli-old/cli.d.ts +0 -3
- package/dist/cli-old/cli.d.ts.map +0 -1
- package/dist/cli-old/cli.js +0 -388
- package/dist/cli-old/cli.js.map +0 -1
- package/dist/commands/review.d.ts +0 -11
- package/dist/commands/review.d.ts.map +0 -1
- package/dist/commands/review.js +0 -82
- package/dist/commands/review.js.map +0 -1
- package/dist/database/connection.d.ts +0 -40
- package/dist/database/connection.d.ts.map +0 -1
- package/dist/database/connection.js +0 -136
- package/dist/database/connection.js.map +0 -1
- package/dist/database/introspection.d.ts +0 -59
- package/dist/database/introspection.d.ts.map +0 -1
- package/dist/database/introspection.js +0 -124
- package/dist/database/introspection.js.map +0 -1
- package/dist/generators/markdown-generator.d.ts +0 -8
- package/dist/generators/markdown-generator.d.ts.map +0 -1
- package/dist/generators/markdown-generator.js +0 -106
- package/dist/generators/markdown-generator.js.map +0 -1
- package/dist/generators/sql-generator.d.ts +0 -20
- package/dist/generators/sql-generator.d.ts.map +0 -1
- package/dist/generators/sql-generator.js +0 -83
- package/dist/generators/sql-generator.js.map +0 -1
- package/dist/state/state-manager.d.ts +0 -95
- package/dist/state/state-manager.d.ts.map +0 -1
- package/dist/state/state-manager.js +0 -236
- package/dist/state/state-manager.js.map +0 -1
- package/dist/types/state-file.d.ts +0 -124
- package/dist/types/state-file.d.ts.map +0 -1
- package/dist/types/state-file.js +0 -79
- package/dist/types/state-file.js.map +0 -1
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PostgreSQL implementation of the BaseAutoDocDriver
|
|
4
|
+
* Uses pg driver for database connectivity
|
|
5
|
+
*/
|
|
6
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
7
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
9
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
10
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
11
|
+
};
|
|
12
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
13
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.PostgreSQLDriver = void 0;
|
|
17
|
+
const pg_1 = require("pg");
|
|
18
|
+
const global_1 = require("@memberjunction/global");
|
|
19
|
+
const BaseAutoDocDriver_js_1 = require("./BaseAutoDocDriver.js");
|
|
20
|
+
/**
|
|
21
|
+
* PostgreSQL driver implementation
|
|
22
|
+
* Registered with MJGlobal for factory instantiation
|
|
23
|
+
*/
|
|
24
|
+
let PostgreSQLDriver = class PostgreSQLDriver extends BaseAutoDocDriver_js_1.BaseAutoDocDriver {
|
|
25
|
+
constructor(config) {
|
|
26
|
+
super(config);
|
|
27
|
+
this.pool = null;
|
|
28
|
+
// Map generic config to PostgreSQL specific config
|
|
29
|
+
this.pgConfig = {
|
|
30
|
+
host: config.host,
|
|
31
|
+
port: config.port || 5432,
|
|
32
|
+
database: config.database,
|
|
33
|
+
user: config.user || config.username,
|
|
34
|
+
password: config.password,
|
|
35
|
+
ssl: config.ssl,
|
|
36
|
+
connectionTimeoutMillis: config.connectionTimeout ?? 30000,
|
|
37
|
+
max: config.maxConnections ?? 10,
|
|
38
|
+
min: config.minConnections ?? 0,
|
|
39
|
+
idleTimeoutMillis: config.idleTimeoutMillis ?? 30000
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// CONNECTION MANAGEMENT
|
|
44
|
+
// ============================================================================
|
|
45
|
+
async connect() {
|
|
46
|
+
if (this.pool) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.pool = new pg_1.Pool(this.pgConfig);
|
|
50
|
+
}
|
|
51
|
+
async test() {
|
|
52
|
+
try {
|
|
53
|
+
await this.connect();
|
|
54
|
+
const result = await this.executeQuery(`
|
|
55
|
+
SELECT
|
|
56
|
+
1 as test,
|
|
57
|
+
version() as version,
|
|
58
|
+
current_database() as db
|
|
59
|
+
`);
|
|
60
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
message: `Successfully connected to ${result.data[0].db} on ${this.config.host}`,
|
|
64
|
+
serverVersion: result.data[0].version,
|
|
65
|
+
databaseName: result.data[0].db
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
message: 'Connection established but test query failed'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
message: `Connection failed: ${error.message}`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async close() {
|
|
81
|
+
if (this.pool) {
|
|
82
|
+
await this.pool.end();
|
|
83
|
+
this.pool = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// SCHEMA INTROSPECTION
|
|
88
|
+
// ============================================================================
|
|
89
|
+
async getSchemas(schemaFilter, tableFilter) {
|
|
90
|
+
// Get all tables grouped by schema
|
|
91
|
+
const tablesQuery = this.buildTablesQuery(schemaFilter, tableFilter);
|
|
92
|
+
const tablesResult = await this.executeQuery(tablesQuery);
|
|
93
|
+
if (!tablesResult.success || !tablesResult.data) {
|
|
94
|
+
throw new Error(`Failed to get tables: ${tablesResult.errorMessage}`);
|
|
95
|
+
}
|
|
96
|
+
// Group tables by schema
|
|
97
|
+
const schemaMap = new Map();
|
|
98
|
+
for (const row of tablesResult.data) {
|
|
99
|
+
if (!schemaMap.has(row.schema_name)) {
|
|
100
|
+
schemaMap.set(row.schema_name, []);
|
|
101
|
+
}
|
|
102
|
+
schemaMap.get(row.schema_name).push({
|
|
103
|
+
tableName: row.table_name,
|
|
104
|
+
rowCount: row.row_count
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Build schemas with full table details
|
|
108
|
+
const schemas = [];
|
|
109
|
+
for (const [schemaName, tableSummaries] of schemaMap) {
|
|
110
|
+
const tables = [];
|
|
111
|
+
for (const { tableName, rowCount } of tableSummaries) {
|
|
112
|
+
const [columns, foreignKeys, primaryKeys] = await Promise.all([
|
|
113
|
+
this.getColumns(schemaName, tableName),
|
|
114
|
+
this.getForeignKeys(schemaName, tableName),
|
|
115
|
+
this.getPrimaryKeys(schemaName, tableName)
|
|
116
|
+
]);
|
|
117
|
+
tables.push({
|
|
118
|
+
schemaName,
|
|
119
|
+
tableName,
|
|
120
|
+
rowCount,
|
|
121
|
+
columns,
|
|
122
|
+
foreignKeys,
|
|
123
|
+
primaryKeys
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
schemas.push({
|
|
127
|
+
name: schemaName,
|
|
128
|
+
tables
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return schemas;
|
|
132
|
+
}
|
|
133
|
+
async getTables(schemaName, tableFilter) {
|
|
134
|
+
const query = this.buildTablesQuery({ include: [schemaName] }, tableFilter);
|
|
135
|
+
const result = await this.executeQuery(query);
|
|
136
|
+
if (!result.success || !result.data) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const tables = [];
|
|
140
|
+
for (const row of result.data) {
|
|
141
|
+
const [columns, foreignKeys, primaryKeys] = await Promise.all([
|
|
142
|
+
this.getColumns(row.schema_name, row.table_name),
|
|
143
|
+
this.getForeignKeys(row.schema_name, row.table_name),
|
|
144
|
+
this.getPrimaryKeys(row.schema_name, row.table_name)
|
|
145
|
+
]);
|
|
146
|
+
tables.push({
|
|
147
|
+
schemaName: row.schema_name,
|
|
148
|
+
tableName: row.table_name,
|
|
149
|
+
rowCount: row.row_count,
|
|
150
|
+
columns,
|
|
151
|
+
foreignKeys,
|
|
152
|
+
primaryKeys
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return tables;
|
|
156
|
+
}
|
|
157
|
+
async getColumns(schemaName, tableName) {
|
|
158
|
+
const query = `
|
|
159
|
+
SELECT
|
|
160
|
+
c.column_name,
|
|
161
|
+
c.data_type,
|
|
162
|
+
c.udt_name,
|
|
163
|
+
CASE WHEN c.is_nullable = 'YES' THEN true ELSE false END as is_nullable,
|
|
164
|
+
c.character_maximum_length,
|
|
165
|
+
c.numeric_precision,
|
|
166
|
+
c.numeric_scale,
|
|
167
|
+
c.column_default,
|
|
168
|
+
CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END as is_primary_key,
|
|
169
|
+
CASE WHEN fk.column_name IS NOT NULL THEN true ELSE false END as is_foreign_key,
|
|
170
|
+
cc.check_clause as check_constraint
|
|
171
|
+
FROM information_schema.columns c
|
|
172
|
+
LEFT JOIN (
|
|
173
|
+
SELECT
|
|
174
|
+
kcu.table_schema,
|
|
175
|
+
kcu.table_name,
|
|
176
|
+
kcu.column_name
|
|
177
|
+
FROM information_schema.table_constraints tc
|
|
178
|
+
JOIN information_schema.key_column_usage kcu
|
|
179
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
180
|
+
AND tc.table_schema = kcu.table_schema
|
|
181
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
182
|
+
) pk ON c.table_schema = pk.table_schema
|
|
183
|
+
AND c.table_name = pk.table_name
|
|
184
|
+
AND c.column_name = pk.column_name
|
|
185
|
+
LEFT JOIN (
|
|
186
|
+
SELECT
|
|
187
|
+
kcu.table_schema,
|
|
188
|
+
kcu.table_name,
|
|
189
|
+
kcu.column_name
|
|
190
|
+
FROM information_schema.table_constraints tc
|
|
191
|
+
JOIN information_schema.key_column_usage kcu
|
|
192
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
193
|
+
AND tc.table_schema = kcu.table_schema
|
|
194
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
195
|
+
) fk ON c.table_schema = fk.table_schema
|
|
196
|
+
AND c.table_name = fk.table_name
|
|
197
|
+
AND c.column_name = fk.column_name
|
|
198
|
+
LEFT JOIN (
|
|
199
|
+
SELECT
|
|
200
|
+
ccu.table_schema,
|
|
201
|
+
ccu.table_name,
|
|
202
|
+
ccu.column_name,
|
|
203
|
+
cc.check_clause
|
|
204
|
+
FROM information_schema.check_constraints cc
|
|
205
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
206
|
+
ON cc.constraint_name = ccu.constraint_name
|
|
207
|
+
AND cc.constraint_schema = ccu.constraint_schema
|
|
208
|
+
) cc ON c.table_schema = cc.table_schema
|
|
209
|
+
AND c.table_name = cc.table_name
|
|
210
|
+
AND c.column_name = cc.column_name
|
|
211
|
+
WHERE c.table_schema = $1 AND c.table_name = $2
|
|
212
|
+
ORDER BY c.ordinal_position
|
|
213
|
+
`;
|
|
214
|
+
const result = await this.executeQuery(query, 3, [schemaName, tableName]);
|
|
215
|
+
if (!result.success || !result.data) {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
return result.data.map(row => ({
|
|
219
|
+
name: row.column_name,
|
|
220
|
+
dataType: this.normalizeDataType(row.data_type, row.udt_name),
|
|
221
|
+
isNullable: row.is_nullable,
|
|
222
|
+
isPrimaryKey: row.is_primary_key,
|
|
223
|
+
isForeignKey: row.is_foreign_key,
|
|
224
|
+
checkConstraint: row.check_constraint || undefined,
|
|
225
|
+
defaultValue: row.column_default || undefined,
|
|
226
|
+
maxLength: row.character_maximum_length || undefined,
|
|
227
|
+
precision: row.numeric_precision || undefined,
|
|
228
|
+
scale: row.numeric_scale || undefined
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
async getExistingDescriptions(schemaName, tableName) {
|
|
232
|
+
// PostgreSQL uses pg_description for comments
|
|
233
|
+
const query = `
|
|
234
|
+
SELECT
|
|
235
|
+
COALESCE(a.attname, '') as column_name,
|
|
236
|
+
d.description
|
|
237
|
+
FROM pg_description d
|
|
238
|
+
JOIN pg_class c ON d.objoid = c.oid
|
|
239
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
240
|
+
LEFT JOIN pg_attribute a ON d.objoid = a.attrelid AND d.objsubid = a.attnum
|
|
241
|
+
WHERE n.nspname = $1
|
|
242
|
+
AND c.relname = $2
|
|
243
|
+
AND d.description IS NOT NULL
|
|
244
|
+
`;
|
|
245
|
+
const result = await this.executeQuery(query, 3, [schemaName, tableName]);
|
|
246
|
+
if (!result.success || !result.data) {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
return result.data.map(row => ({
|
|
250
|
+
target: row.column_name ? 'column' : 'table',
|
|
251
|
+
targetName: row.column_name,
|
|
252
|
+
description: row.description
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// DATA SAMPLING AND STATISTICS
|
|
257
|
+
// ============================================================================
|
|
258
|
+
async getColumnStatistics(schemaName, tableName, columnName, dataType, cardinalityThreshold, sampleSize) {
|
|
259
|
+
const stats = {
|
|
260
|
+
totalRows: 0,
|
|
261
|
+
distinctCount: 0,
|
|
262
|
+
uniquenessRatio: 0,
|
|
263
|
+
nullCount: 0,
|
|
264
|
+
nullPercentage: 0,
|
|
265
|
+
sampleValues: []
|
|
266
|
+
};
|
|
267
|
+
// Get cardinality and null statistics
|
|
268
|
+
const cardinalityStats = await this.getCardinalityStats(schemaName, tableName, columnName);
|
|
269
|
+
Object.assign(stats, cardinalityStats);
|
|
270
|
+
// Get value distribution for low-cardinality columns
|
|
271
|
+
if (stats.distinctCount <= cardinalityThreshold && stats.distinctCount > 0) {
|
|
272
|
+
const distribution = await this.getValueDistribution(schemaName, tableName, columnName, 100);
|
|
273
|
+
stats.valueDistribution = distribution.map(d => ({
|
|
274
|
+
...d,
|
|
275
|
+
percentage: (d.frequency / (cardinalityStats.totalCount || 1)) * 100
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
// Get type-specific statistics
|
|
279
|
+
if (this.isNumericType(dataType)) {
|
|
280
|
+
const numericStats = await this.getNumericStats(schemaName, tableName, columnName);
|
|
281
|
+
Object.assign(stats, numericStats);
|
|
282
|
+
}
|
|
283
|
+
else if (this.isDateType(dataType)) {
|
|
284
|
+
const dateStats = await this.getDateStats(schemaName, tableName, columnName);
|
|
285
|
+
Object.assign(stats, dateStats);
|
|
286
|
+
}
|
|
287
|
+
else if (this.isStringType(dataType)) {
|
|
288
|
+
const stringStats = await this.getStringStats(schemaName, tableName, columnName);
|
|
289
|
+
Object.assign(stats, stringStats);
|
|
290
|
+
}
|
|
291
|
+
// Get sample values
|
|
292
|
+
stats.sampleValues = await this.getSampleValues(schemaName, tableName, columnName, sampleSize);
|
|
293
|
+
return stats;
|
|
294
|
+
}
|
|
295
|
+
async getDistinctCount(schemaName, tableName, columnName) {
|
|
296
|
+
const query = `
|
|
297
|
+
SELECT COUNT(DISTINCT ${this.escapeIdentifier(columnName)}) as distinct_count
|
|
298
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
299
|
+
`;
|
|
300
|
+
const result = await this.executeQuery(query);
|
|
301
|
+
return result.success && result.data && result.data.length > 0 ? Number(result.data[0].distinct_count) : 0;
|
|
302
|
+
}
|
|
303
|
+
async getValueDistribution(schemaName, tableName, columnName, limit) {
|
|
304
|
+
const query = `
|
|
305
|
+
SELECT
|
|
306
|
+
${this.escapeIdentifier(columnName)} as value,
|
|
307
|
+
COUNT(*) as frequency
|
|
308
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
309
|
+
WHERE ${this.escapeIdentifier(columnName)} IS NOT NULL
|
|
310
|
+
GROUP BY ${this.escapeIdentifier(columnName)}
|
|
311
|
+
ORDER BY COUNT(*) DESC
|
|
312
|
+
LIMIT ${limit}
|
|
313
|
+
`;
|
|
314
|
+
const result = await this.executeQuery(query);
|
|
315
|
+
return result.success && result.data ? result.data.map(r => ({
|
|
316
|
+
value: r.value,
|
|
317
|
+
frequency: Number(r.frequency)
|
|
318
|
+
})) : [];
|
|
319
|
+
}
|
|
320
|
+
async getSampleValues(schemaName, tableName, columnName, sampleSize) {
|
|
321
|
+
// Limit sample size to max 20 to reduce JSON size
|
|
322
|
+
const limitedSampleSize = Math.min(sampleSize, 20);
|
|
323
|
+
const query = `
|
|
324
|
+
SELECT ${this.escapeIdentifier(columnName)} as value
|
|
325
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
326
|
+
WHERE ${this.escapeIdentifier(columnName)} IS NOT NULL
|
|
327
|
+
ORDER BY RANDOM()
|
|
328
|
+
LIMIT ${limitedSampleSize}
|
|
329
|
+
`;
|
|
330
|
+
const result = await this.executeQuery(query);
|
|
331
|
+
return result.success && result.data ? result.data.map(r => r.value) : [];
|
|
332
|
+
}
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// QUERY EXECUTION
|
|
335
|
+
// ============================================================================
|
|
336
|
+
async executeQuery(query, maxRetries = 3, params) {
|
|
337
|
+
let lastError = null;
|
|
338
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
339
|
+
try {
|
|
340
|
+
if (!this.pool) {
|
|
341
|
+
await this.connect();
|
|
342
|
+
}
|
|
343
|
+
const result = params
|
|
344
|
+
? await this.pool.query(query, params)
|
|
345
|
+
: await this.pool.query(query);
|
|
346
|
+
return {
|
|
347
|
+
success: true,
|
|
348
|
+
data: result.rows,
|
|
349
|
+
rowCount: result.rowCount || 0
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
lastError = error;
|
|
354
|
+
// Check if error is transient
|
|
355
|
+
if (this.isTransientError(error) && attempt < maxRetries) {
|
|
356
|
+
await this.sleep(Math.pow(2, attempt) * 1000);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
success: false,
|
|
364
|
+
errorMessage: lastError?.message || 'Unknown error'
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
// ============================================================================
|
|
368
|
+
// PROVIDER-SPECIFIC HELPERS
|
|
369
|
+
// ============================================================================
|
|
370
|
+
escapeIdentifier(identifier) {
|
|
371
|
+
return `"${identifier}"`;
|
|
372
|
+
}
|
|
373
|
+
getLimitClause(limit) {
|
|
374
|
+
return `LIMIT ${limit}`;
|
|
375
|
+
}
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// PRIVATE HELPER METHODS
|
|
378
|
+
// ============================================================================
|
|
379
|
+
buildTablesQuery(schemaFilter, tableFilter) {
|
|
380
|
+
let whereClause = "WHERE t.table_type = 'BASE TABLE'";
|
|
381
|
+
whereClause += this.buildSchemaFilterClause(schemaFilter, 't.table_schema');
|
|
382
|
+
whereClause += this.buildTableFilterClause(tableFilter, 't.table_name');
|
|
383
|
+
// Exclude system schemas by default
|
|
384
|
+
if (!schemaFilter.include || schemaFilter.include.length === 0) {
|
|
385
|
+
whereClause += ` AND t.table_schema NOT IN ('pg_catalog', 'information_schema')`;
|
|
386
|
+
}
|
|
387
|
+
return `
|
|
388
|
+
SELECT
|
|
389
|
+
t.table_schema as schema_name,
|
|
390
|
+
t.table_name as table_name,
|
|
391
|
+
COALESCE(s.n_live_tup, 0) as row_count
|
|
392
|
+
FROM information_schema.tables t
|
|
393
|
+
LEFT JOIN pg_stat_user_tables s
|
|
394
|
+
ON t.table_schema = s.schemaname
|
|
395
|
+
AND t.table_name = s.relname
|
|
396
|
+
${whereClause}
|
|
397
|
+
ORDER BY t.table_schema, t.table_name
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
400
|
+
async getForeignKeys(schemaName, tableName) {
|
|
401
|
+
const query = `
|
|
402
|
+
SELECT
|
|
403
|
+
kcu.column_name,
|
|
404
|
+
ccu.table_schema as referenced_schema,
|
|
405
|
+
ccu.table_name as referenced_table,
|
|
406
|
+
ccu.column_name as referenced_column,
|
|
407
|
+
tc.constraint_name
|
|
408
|
+
FROM information_schema.table_constraints tc
|
|
409
|
+
JOIN information_schema.key_column_usage kcu
|
|
410
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
411
|
+
AND tc.table_schema = kcu.table_schema
|
|
412
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
413
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
414
|
+
AND ccu.table_schema = tc.table_schema
|
|
415
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
416
|
+
AND tc.table_schema = $1
|
|
417
|
+
AND tc.table_name = $2
|
|
418
|
+
`;
|
|
419
|
+
const result = await this.executeQuery(query, 3, [schemaName, tableName]);
|
|
420
|
+
if (!result.success || !result.data) {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
return result.data.map(row => ({
|
|
424
|
+
columnName: row.column_name,
|
|
425
|
+
referencedSchema: row.referenced_schema,
|
|
426
|
+
referencedTable: row.referenced_table,
|
|
427
|
+
referencedColumn: row.referenced_column,
|
|
428
|
+
constraintName: row.constraint_name
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
async getPrimaryKeys(schemaName, tableName) {
|
|
432
|
+
const query = `
|
|
433
|
+
SELECT
|
|
434
|
+
kcu.column_name,
|
|
435
|
+
kcu.ordinal_position,
|
|
436
|
+
tc.constraint_name
|
|
437
|
+
FROM information_schema.table_constraints tc
|
|
438
|
+
JOIN information_schema.key_column_usage kcu
|
|
439
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
440
|
+
AND tc.table_schema = kcu.table_schema
|
|
441
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
442
|
+
AND tc.table_schema = $1
|
|
443
|
+
AND tc.table_name = $2
|
|
444
|
+
ORDER BY kcu.ordinal_position
|
|
445
|
+
`;
|
|
446
|
+
const result = await this.executeQuery(query, 3, [schemaName, tableName]);
|
|
447
|
+
if (!result.success || !result.data) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
return result.data.map(row => ({
|
|
451
|
+
columnName: row.column_name,
|
|
452
|
+
ordinalPosition: row.ordinal_position,
|
|
453
|
+
constraintName: row.constraint_name
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
async getCardinalityStats(schemaName, tableName, columnName) {
|
|
457
|
+
const query = `
|
|
458
|
+
SELECT
|
|
459
|
+
COUNT(DISTINCT ${this.escapeIdentifier(columnName)}) as distinct_count,
|
|
460
|
+
COUNT(*) as total_count,
|
|
461
|
+
COUNT(*) - COUNT(${this.escapeIdentifier(columnName)}) as null_count
|
|
462
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
463
|
+
`;
|
|
464
|
+
const result = await this.executeQuery(query);
|
|
465
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
466
|
+
const row = result.data[0];
|
|
467
|
+
const totalCount = Number(row.total_count) || 1;
|
|
468
|
+
const distinctCount = Number(row.distinct_count);
|
|
469
|
+
const nullCount = Number(row.null_count);
|
|
470
|
+
return {
|
|
471
|
+
distinctCount,
|
|
472
|
+
uniquenessRatio: distinctCount / totalCount,
|
|
473
|
+
nullCount,
|
|
474
|
+
nullPercentage: (nullCount / totalCount) * 100,
|
|
475
|
+
totalCount
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
distinctCount: 0,
|
|
480
|
+
uniquenessRatio: 0,
|
|
481
|
+
nullCount: 0,
|
|
482
|
+
nullPercentage: 0,
|
|
483
|
+
totalCount: 0
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
async getNumericStats(schemaName, tableName, columnName) {
|
|
487
|
+
const query = `
|
|
488
|
+
SELECT
|
|
489
|
+
MIN(${this.escapeIdentifier(columnName)}) as min_value,
|
|
490
|
+
MAX(${this.escapeIdentifier(columnName)}) as max_value,
|
|
491
|
+
AVG(${this.escapeIdentifier(columnName)}::NUMERIC) as avg_value,
|
|
492
|
+
STDDEV(${this.escapeIdentifier(columnName)}) as std_dev
|
|
493
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
494
|
+
WHERE ${this.escapeIdentifier(columnName)} IS NOT NULL
|
|
495
|
+
`;
|
|
496
|
+
const result = await this.executeQuery(query);
|
|
497
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
498
|
+
const row = result.data[0];
|
|
499
|
+
return {
|
|
500
|
+
min: row.min_value,
|
|
501
|
+
max: row.max_value,
|
|
502
|
+
avg: row.avg_value ? Number(row.avg_value) : undefined,
|
|
503
|
+
stdDev: row.std_dev ? Number(row.std_dev) : undefined
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return {};
|
|
507
|
+
}
|
|
508
|
+
async getDateStats(schemaName, tableName, columnName) {
|
|
509
|
+
const query = `
|
|
510
|
+
SELECT
|
|
511
|
+
MIN(${this.escapeIdentifier(columnName)}) as min_value,
|
|
512
|
+
MAX(${this.escapeIdentifier(columnName)}) as max_value
|
|
513
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
514
|
+
WHERE ${this.escapeIdentifier(columnName)} IS NOT NULL
|
|
515
|
+
`;
|
|
516
|
+
const result = await this.executeQuery(query);
|
|
517
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
518
|
+
const row = result.data[0];
|
|
519
|
+
return {
|
|
520
|
+
min: row.min_value,
|
|
521
|
+
max: row.max_value
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
return {};
|
|
525
|
+
}
|
|
526
|
+
async getStringStats(schemaName, tableName, columnName) {
|
|
527
|
+
const query = `
|
|
528
|
+
SELECT
|
|
529
|
+
AVG(LENGTH(${this.escapeIdentifier(columnName)})) as avg_length,
|
|
530
|
+
MAX(LENGTH(${this.escapeIdentifier(columnName)})) as max_length,
|
|
531
|
+
MIN(LENGTH(${this.escapeIdentifier(columnName)})) as min_length
|
|
532
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
533
|
+
WHERE ${this.escapeIdentifier(columnName)} IS NOT NULL
|
|
534
|
+
`;
|
|
535
|
+
const result = await this.executeQuery(query);
|
|
536
|
+
if (result.success && result.data && result.data.length > 0) {
|
|
537
|
+
const row = result.data[0];
|
|
538
|
+
return {
|
|
539
|
+
avgLength: row.avg_length ? Number(row.avg_length) : undefined,
|
|
540
|
+
maxLength: row.max_length,
|
|
541
|
+
minLength: row.min_length
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return {};
|
|
545
|
+
}
|
|
546
|
+
isTransientError(error) {
|
|
547
|
+
const transientMessages = [
|
|
548
|
+
'connection',
|
|
549
|
+
'timeout',
|
|
550
|
+
'deadlock',
|
|
551
|
+
'network',
|
|
552
|
+
'ECONNREFUSED',
|
|
553
|
+
'ENOTFOUND',
|
|
554
|
+
'ETIMEDOUT'
|
|
555
|
+
];
|
|
556
|
+
const message = error.message.toLowerCase();
|
|
557
|
+
return transientMessages.some(msg => message.includes(msg.toLowerCase()));
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Normalize PostgreSQL data types to generic format
|
|
561
|
+
*/
|
|
562
|
+
normalizeDataType(dataType, udtName) {
|
|
563
|
+
// Map PostgreSQL types to more generic names
|
|
564
|
+
const typeMap = {
|
|
565
|
+
'character varying': 'varchar',
|
|
566
|
+
'character': 'char',
|
|
567
|
+
'timestamp without time zone': 'timestamp',
|
|
568
|
+
'timestamp with time zone': 'timestamptz',
|
|
569
|
+
'time without time zone': 'time',
|
|
570
|
+
'time with time zone': 'timetz',
|
|
571
|
+
'double precision': 'float8',
|
|
572
|
+
'integer': 'int4',
|
|
573
|
+
'bigint': 'int8',
|
|
574
|
+
'smallint': 'int2'
|
|
575
|
+
};
|
|
576
|
+
return typeMap[dataType.toLowerCase()] || udtName || dataType;
|
|
577
|
+
}
|
|
578
|
+
// ============================================================================
|
|
579
|
+
// RELATIONSHIP DISCOVERY METHODS
|
|
580
|
+
// ============================================================================
|
|
581
|
+
/**
|
|
582
|
+
* Get column information for relationship discovery
|
|
583
|
+
*/
|
|
584
|
+
async getColumnInfo(schemaName, tableName, columnName) {
|
|
585
|
+
const query = `
|
|
586
|
+
SELECT
|
|
587
|
+
column_name as name,
|
|
588
|
+
data_type as type,
|
|
589
|
+
CASE WHEN is_nullable = 'YES' THEN true ELSE false END as nullable
|
|
590
|
+
FROM information_schema.columns
|
|
591
|
+
WHERE table_schema = $1
|
|
592
|
+
AND table_name = $2
|
|
593
|
+
AND column_name = $3
|
|
594
|
+
`;
|
|
595
|
+
const result = await this.executeQuery(query, 3, [schemaName, tableName, columnName]);
|
|
596
|
+
if (!result.success || !result.data || result.data.length === 0) {
|
|
597
|
+
throw new Error(`Column ${schemaName}.${tableName}.${columnName} not found`);
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
name: result.data[0].name,
|
|
601
|
+
type: result.data[0].type,
|
|
602
|
+
nullable: result.data[0].nullable
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Test value overlap between source and target columns
|
|
607
|
+
*/
|
|
608
|
+
async testValueOverlap(sourceTable, sourceColumn, targetTable, targetColumn, sampleSize) {
|
|
609
|
+
try {
|
|
610
|
+
const [sourceSchema, sourceTableName] = this.parseTableIdentifier(sourceTable);
|
|
611
|
+
const [targetSchema, targetTableName] = this.parseTableIdentifier(targetTable);
|
|
612
|
+
const query = `
|
|
613
|
+
WITH source_sample AS (
|
|
614
|
+
SELECT DISTINCT ${this.escapeIdentifier(sourceColumn)} as value
|
|
615
|
+
FROM ${this.escapeIdentifier(sourceSchema)}.${this.escapeIdentifier(sourceTableName)}
|
|
616
|
+
WHERE ${this.escapeIdentifier(sourceColumn)} IS NOT NULL
|
|
617
|
+
ORDER BY RANDOM()
|
|
618
|
+
LIMIT ${sampleSize}
|
|
619
|
+
),
|
|
620
|
+
target_values AS (
|
|
621
|
+
SELECT DISTINCT ${this.escapeIdentifier(targetColumn)} as value
|
|
622
|
+
FROM ${this.escapeIdentifier(targetSchema)}.${this.escapeIdentifier(targetTableName)}
|
|
623
|
+
WHERE ${this.escapeIdentifier(targetColumn)} IS NOT NULL
|
|
624
|
+
)
|
|
625
|
+
SELECT
|
|
626
|
+
COUNT(*) as total_source,
|
|
627
|
+
SUM(CASE WHEN tv.value IS NOT NULL THEN 1 ELSE 0 END) as matching_count
|
|
628
|
+
FROM source_sample ss
|
|
629
|
+
LEFT JOIN target_values tv ON ss.value = tv.value
|
|
630
|
+
`;
|
|
631
|
+
const result = await this.executeQuery(query);
|
|
632
|
+
if (!result.success || !result.data || result.data.length === 0) {
|
|
633
|
+
return 0;
|
|
634
|
+
}
|
|
635
|
+
const row = result.data[0];
|
|
636
|
+
const totalSource = Number(row.total_source);
|
|
637
|
+
const matchingCount = Number(row.matching_count);
|
|
638
|
+
if (totalSource === 0) {
|
|
639
|
+
return 0;
|
|
640
|
+
}
|
|
641
|
+
return matchingCount / totalSource;
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
return 0;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Check if combination of columns is unique
|
|
649
|
+
*/
|
|
650
|
+
async checkColumnCombinationUniqueness(schemaName, tableName, columnNames, sampleSize) {
|
|
651
|
+
try {
|
|
652
|
+
if (columnNames.length === 0) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
const escapedColumns = columnNames.map(col => this.escapeIdentifier(col));
|
|
656
|
+
const columnList = escapedColumns.join(', ');
|
|
657
|
+
const whereClause = escapedColumns.map(col => `${col} IS NOT NULL`).join(' AND ');
|
|
658
|
+
const query = `
|
|
659
|
+
WITH sampled_data AS (
|
|
660
|
+
SELECT
|
|
661
|
+
${columnList}
|
|
662
|
+
FROM ${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}
|
|
663
|
+
WHERE ${whereClause}
|
|
664
|
+
ORDER BY RANDOM()
|
|
665
|
+
LIMIT ${sampleSize}
|
|
666
|
+
),
|
|
667
|
+
grouped_data AS (
|
|
668
|
+
SELECT
|
|
669
|
+
${columnList},
|
|
670
|
+
COUNT(*) as occurrence_count
|
|
671
|
+
FROM sampled_data
|
|
672
|
+
GROUP BY ${columnList}
|
|
673
|
+
HAVING COUNT(*) > 1
|
|
674
|
+
)
|
|
675
|
+
SELECT COUNT(*) as duplicate_count
|
|
676
|
+
FROM grouped_data
|
|
677
|
+
`;
|
|
678
|
+
const result = await this.executeQuery(query);
|
|
679
|
+
if (!result.success || !result.data || result.data.length === 0) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
return Number(result.data[0].duplicate_count) === 0;
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Parse table identifier in format "schema.table"
|
|
690
|
+
*/
|
|
691
|
+
parseTableIdentifier(tableIdentifier) {
|
|
692
|
+
const parts = tableIdentifier.split('.');
|
|
693
|
+
if (parts.length !== 2) {
|
|
694
|
+
throw new Error(`Invalid table identifier format: ${tableIdentifier}. Expected "schema.table"`);
|
|
695
|
+
}
|
|
696
|
+
return [parts[0], parts[1]];
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
exports.PostgreSQLDriver = PostgreSQLDriver;
|
|
700
|
+
exports.PostgreSQLDriver = PostgreSQLDriver = __decorate([
|
|
701
|
+
(0, global_1.RegisterClass)(BaseAutoDocDriver_js_1.BaseAutoDocDriver, 'PostgreSQL'),
|
|
702
|
+
__metadata("design:paramtypes", [Object])
|
|
703
|
+
], PostgreSQLDriver);
|
|
704
|
+
//# sourceMappingURL=PostgreSQLDriver.js.map
|