@zz1996/dbhub-dameng 0.1.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.
@@ -0,0 +1,564 @@
1
+ import {
2
+ quoteIdentifier
3
+ } from "./chunk-IPK7BYBL.js";
4
+ import {
5
+ SQLRowLimiter
6
+ } from "./chunk-ZNQTMARG.js";
7
+ import {
8
+ ConnectorRegistry,
9
+ SafeURL,
10
+ obfuscateDSNPassword,
11
+ splitSQLStatements
12
+ } from "./chunk-SQA2ISDE.js";
13
+
14
+ // src/connectors/postgres/index.ts
15
+ import fs from "fs";
16
+ import os from "os";
17
+ import path from "path";
18
+ import pg from "pg";
19
+
20
+ // src/connectors/postgres/failed-to-read-certificate.ts
21
+ var FailedToReadCertificate = class extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = "FailedToReadCertificate";
25
+ }
26
+ };
27
+
28
+ // src/connectors/postgres/index.ts
29
+ var { Pool } = pg;
30
+ var PostgresDSNParser = class {
31
+ async parse(dsn, config) {
32
+ const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
33
+ const queryTimeoutSeconds = config?.queryTimeoutSeconds;
34
+ if (!this.isValidDSN(dsn)) {
35
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
36
+ const expectedFormat = this.getSampleDSN();
37
+ throw new Error(
38
+ `Invalid PostgreSQL DSN format.
39
+ Provided: ${obfuscatedDSN}
40
+ Expected: ${expectedFormat}`
41
+ );
42
+ }
43
+ try {
44
+ const url = new SafeURL(dsn);
45
+ const poolConfig = {
46
+ host: url.hostname,
47
+ port: url.port ? parseInt(url.port) : 5432,
48
+ database: url.pathname ? url.pathname.substring(1) : "",
49
+ // Remove leading '/' if exists
50
+ user: url.username,
51
+ password: url.password
52
+ };
53
+ let sslmode;
54
+ let sslrootcert;
55
+ url.forEachSearchParam((value, key) => {
56
+ if (key === "sslmode") {
57
+ sslmode = value;
58
+ } else if (key === "sslrootcert") {
59
+ sslrootcert = value;
60
+ }
61
+ });
62
+ if (sslmode === "disable") {
63
+ poolConfig.ssl = false;
64
+ } else if (sslmode === "require") {
65
+ poolConfig.ssl = { rejectUnauthorized: false };
66
+ } else if (sslmode === "verify-ca" || sslmode === "verify-full") {
67
+ const sslConfig = { rejectUnauthorized: true };
68
+ if (sslmode === "verify-ca") {
69
+ sslConfig.checkServerIdentity = () => void 0;
70
+ }
71
+ if (sslrootcert) {
72
+ const certPath = sslrootcert.startsWith("~/") ? path.join(os.homedir(), sslrootcert.slice(2)) : sslrootcert;
73
+ try {
74
+ sslConfig.ca = await fs.promises.readFile(certPath, "utf-8");
75
+ } catch (err) {
76
+ throw new FailedToReadCertificate(
77
+ `Failed to read SSL root certificate at '${certPath}': ${err instanceof Error ? err.message : String(err)}`
78
+ );
79
+ }
80
+ }
81
+ poolConfig.ssl = sslConfig;
82
+ } else if (sslmode !== void 0) {
83
+ poolConfig.ssl = true;
84
+ }
85
+ if (connectionTimeoutSeconds !== void 0) {
86
+ poolConfig.connectionTimeoutMillis = connectionTimeoutSeconds * 1e3;
87
+ }
88
+ if (queryTimeoutSeconds !== void 0) {
89
+ poolConfig.query_timeout = queryTimeoutSeconds * 1e3;
90
+ }
91
+ return poolConfig;
92
+ } catch (error) {
93
+ if (error instanceof FailedToReadCertificate) {
94
+ throw error;
95
+ }
96
+ const originalError = error instanceof Error ? error : new Error(String(error));
97
+ throw new Error(
98
+ `Failed to parse PostgreSQL DSN: ${originalError.message}`,
99
+ { cause: originalError }
100
+ );
101
+ }
102
+ }
103
+ getSampleDSN() {
104
+ return "postgres://postgres:password@localhost:5432/postgres?sslmode=require";
105
+ }
106
+ isValidDSN(dsn) {
107
+ try {
108
+ return dsn.startsWith("postgres://") || dsn.startsWith("postgresql://");
109
+ } catch (error) {
110
+ return false;
111
+ }
112
+ }
113
+ };
114
+ var PostgresConnector = class _PostgresConnector {
115
+ constructor() {
116
+ this.id = "postgres";
117
+ this.name = "PostgreSQL";
118
+ this.dsnParser = new PostgresDSNParser();
119
+ this.pool = null;
120
+ // Source ID is set by ConnectorManager after cloning
121
+ this.sourceId = "default";
122
+ // Default schema for discovery methods (first entry from search_path, or "public")
123
+ this.defaultSchema = "public";
124
+ }
125
+ getId() {
126
+ return this.sourceId;
127
+ }
128
+ clone() {
129
+ return new _PostgresConnector();
130
+ }
131
+ async connect(dsn, initScript, config) {
132
+ this.defaultSchema = "public";
133
+ try {
134
+ const poolConfig = await this.dsnParser.parse(dsn, config);
135
+ if (config?.readonly) {
136
+ poolConfig.options = (poolConfig.options || "") + " -c default_transaction_read_only=on";
137
+ }
138
+ if (config?.searchPath) {
139
+ const schemas = config.searchPath.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
140
+ if (schemas.length > 0) {
141
+ this.defaultSchema = schemas[0];
142
+ const quotedSchemas = schemas.map((s) => quoteIdentifier(s, "postgres"));
143
+ const optionsValue = quotedSchemas.join(",").replace(/\\/g, "\\\\").replace(/ /g, "\\ ");
144
+ poolConfig.options = (poolConfig.options || "") + ` -c search_path=${optionsValue}`;
145
+ }
146
+ }
147
+ this.pool = new Pool(poolConfig);
148
+ const client = await this.pool.connect();
149
+ client.release();
150
+ } catch (err) {
151
+ console.error("Failed to connect to PostgreSQL database:", err);
152
+ throw err;
153
+ }
154
+ }
155
+ async disconnect() {
156
+ if (this.pool) {
157
+ await this.pool.end();
158
+ this.pool = null;
159
+ }
160
+ }
161
+ async getSchemas() {
162
+ if (!this.pool) {
163
+ throw new Error("Not connected to database");
164
+ }
165
+ const client = await this.pool.connect();
166
+ try {
167
+ const result = await client.query(`
168
+ SELECT schema_name
169
+ FROM information_schema.schemata
170
+ WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
171
+ ORDER BY schema_name
172
+ `);
173
+ return result.rows.map((row) => row.schema_name);
174
+ } finally {
175
+ client.release();
176
+ }
177
+ }
178
+ async getTables(schema) {
179
+ if (!this.pool) {
180
+ throw new Error("Not connected to database");
181
+ }
182
+ const client = await this.pool.connect();
183
+ try {
184
+ const schemaToUse = schema || this.defaultSchema;
185
+ const result = await client.query(
186
+ `
187
+ SELECT table_name
188
+ FROM information_schema.tables
189
+ WHERE table_schema = $1
190
+ AND table_type = 'BASE TABLE'
191
+ ORDER BY table_name
192
+ `,
193
+ [schemaToUse]
194
+ );
195
+ return result.rows.map((row) => row.table_name);
196
+ } finally {
197
+ client.release();
198
+ }
199
+ }
200
+ async getViews(schema) {
201
+ if (!this.pool) {
202
+ throw new Error("Not connected to database");
203
+ }
204
+ const client = await this.pool.connect();
205
+ try {
206
+ const schemaToUse = schema || this.defaultSchema;
207
+ const result = await client.query(
208
+ `
209
+ SELECT table_name
210
+ FROM information_schema.tables
211
+ WHERE table_schema = $1
212
+ AND table_type = 'VIEW'
213
+ ORDER BY table_name
214
+ `,
215
+ [schemaToUse]
216
+ );
217
+ return result.rows.map((row) => row.table_name);
218
+ } finally {
219
+ client.release();
220
+ }
221
+ }
222
+ async tableExists(tableName, schema) {
223
+ if (!this.pool) {
224
+ throw new Error("Not connected to database");
225
+ }
226
+ const client = await this.pool.connect();
227
+ try {
228
+ const schemaToUse = schema || this.defaultSchema;
229
+ const result = await client.query(
230
+ `
231
+ SELECT EXISTS (
232
+ SELECT FROM information_schema.tables
233
+ WHERE table_schema = $1
234
+ AND table_name = $2
235
+ )
236
+ `,
237
+ [schemaToUse, tableName]
238
+ );
239
+ return result.rows[0].exists;
240
+ } finally {
241
+ client.release();
242
+ }
243
+ }
244
+ async getTableIndexes(tableName, schema) {
245
+ if (!this.pool) {
246
+ throw new Error("Not connected to database");
247
+ }
248
+ const client = await this.pool.connect();
249
+ try {
250
+ const schemaToUse = schema || this.defaultSchema;
251
+ const result = await client.query(
252
+ `
253
+ SELECT
254
+ i.relname as index_name,
255
+ array_agg(a.attname) as column_names,
256
+ ix.indisunique as is_unique,
257
+ ix.indisprimary as is_primary
258
+ FROM
259
+ pg_class t,
260
+ pg_class i,
261
+ pg_index ix,
262
+ pg_attribute a,
263
+ pg_namespace ns
264
+ WHERE
265
+ t.oid = ix.indrelid
266
+ AND i.oid = ix.indexrelid
267
+ AND a.attrelid = t.oid
268
+ AND a.attnum = ANY(ix.indkey)
269
+ AND t.relkind = 'r'
270
+ AND t.relname = $1
271
+ AND ns.oid = t.relnamespace
272
+ AND ns.nspname = $2
273
+ GROUP BY
274
+ i.relname,
275
+ ix.indisunique,
276
+ ix.indisprimary
277
+ ORDER BY
278
+ i.relname
279
+ `,
280
+ [tableName, schemaToUse]
281
+ );
282
+ return result.rows.map((row) => ({
283
+ index_name: row.index_name,
284
+ column_names: row.column_names,
285
+ is_unique: row.is_unique,
286
+ is_primary: row.is_primary
287
+ }));
288
+ } finally {
289
+ client.release();
290
+ }
291
+ }
292
+ async getTableSchema(tableName, schema) {
293
+ if (!this.pool) {
294
+ throw new Error("Not connected to database");
295
+ }
296
+ const client = await this.pool.connect();
297
+ try {
298
+ const schemaToUse = schema || this.defaultSchema;
299
+ const result = await client.query(
300
+ `
301
+ SELECT
302
+ c.column_name,
303
+ c.data_type,
304
+ c.is_nullable,
305
+ c.column_default,
306
+ pgd.description
307
+ FROM information_schema.columns c
308
+ LEFT JOIN pg_catalog.pg_namespace nsp
309
+ ON nsp.nspname = c.table_schema
310
+ LEFT JOIN pg_catalog.pg_class cls
311
+ ON cls.relnamespace = nsp.oid
312
+ AND cls.relname = c.table_name
313
+ LEFT JOIN pg_catalog.pg_description pgd
314
+ ON pgd.objoid = cls.oid
315
+ AND pgd.objsubid = c.ordinal_position
316
+ WHERE c.table_schema = $1
317
+ AND c.table_name = $2
318
+ ORDER BY c.ordinal_position
319
+ `,
320
+ [schemaToUse, tableName]
321
+ );
322
+ return result.rows;
323
+ } finally {
324
+ client.release();
325
+ }
326
+ }
327
+ async getTableRowCount(tableName, schema) {
328
+ if (!this.pool) {
329
+ throw new Error("Not connected to database");
330
+ }
331
+ const client = await this.pool.connect();
332
+ try {
333
+ const schemaToUse = schema || this.defaultSchema;
334
+ const result = await client.query(
335
+ `
336
+ SELECT c.reltuples::bigint as count
337
+ FROM pg_class c
338
+ JOIN pg_namespace n ON n.oid = c.relnamespace
339
+ WHERE c.relname = $1
340
+ AND n.nspname = $2
341
+ AND c.relkind IN ('r','p','m','f')
342
+ `,
343
+ [tableName, schemaToUse]
344
+ );
345
+ if (result.rows.length > 0) {
346
+ const count = Number(result.rows[0].count);
347
+ return count >= 0 ? count : null;
348
+ }
349
+ return null;
350
+ } finally {
351
+ client.release();
352
+ }
353
+ }
354
+ async getTableComment(tableName, schema) {
355
+ if (!this.pool) {
356
+ throw new Error("Not connected to database");
357
+ }
358
+ const client = await this.pool.connect();
359
+ try {
360
+ const schemaToUse = schema || this.defaultSchema;
361
+ const result = await client.query(
362
+ `
363
+ SELECT obj_description(c.oid) as table_comment
364
+ FROM pg_catalog.pg_class c
365
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
366
+ WHERE c.relname = $1
367
+ AND n.nspname = $2
368
+ AND c.relkind IN ('r','p','m','f','v')
369
+ `,
370
+ [tableName, schemaToUse]
371
+ );
372
+ if (result.rows.length > 0) {
373
+ return result.rows[0].table_comment || null;
374
+ }
375
+ return null;
376
+ } finally {
377
+ client.release();
378
+ }
379
+ }
380
+ async getStoredProcedures(schema, routineType) {
381
+ if (!this.pool) {
382
+ throw new Error("Not connected to database");
383
+ }
384
+ const client = await this.pool.connect();
385
+ try {
386
+ const schemaToUse = schema || this.defaultSchema;
387
+ const params = [schemaToUse];
388
+ let typeFilter = "";
389
+ if (routineType === "function") {
390
+ typeFilter = " AND routine_type = 'FUNCTION'";
391
+ } else if (routineType === "procedure") {
392
+ typeFilter = " AND routine_type = 'PROCEDURE'";
393
+ }
394
+ const result = await client.query(
395
+ `
396
+ SELECT
397
+ routine_name
398
+ FROM information_schema.routines
399
+ WHERE routine_schema = $1${typeFilter}
400
+ ORDER BY routine_name
401
+ `,
402
+ params
403
+ );
404
+ return result.rows.map((row) => row.routine_name);
405
+ } finally {
406
+ client.release();
407
+ }
408
+ }
409
+ async getStoredProcedureDetail(procedureName, schema) {
410
+ if (!this.pool) {
411
+ throw new Error("Not connected to database");
412
+ }
413
+ const client = await this.pool.connect();
414
+ try {
415
+ const schemaToUse = schema || this.defaultSchema;
416
+ const result = await client.query(
417
+ `
418
+ SELECT
419
+ routine_name as procedure_name,
420
+ routine_type,
421
+ CASE WHEN routine_type = 'PROCEDURE' THEN 'procedure' ELSE 'function' END as procedure_type,
422
+ external_language as language,
423
+ data_type as return_type,
424
+ routine_definition as definition,
425
+ (
426
+ SELECT string_agg(
427
+ parameter_name || ' ' ||
428
+ parameter_mode || ' ' ||
429
+ data_type,
430
+ ', '
431
+ )
432
+ FROM information_schema.parameters
433
+ WHERE specific_schema = $1
434
+ AND specific_name = $2
435
+ AND parameter_name IS NOT NULL
436
+ ) as parameter_list
437
+ FROM information_schema.routines
438
+ WHERE routine_schema = $1
439
+ AND routine_name = $2
440
+ `,
441
+ [schemaToUse, procedureName]
442
+ );
443
+ if (result.rows.length === 0) {
444
+ throw new Error(`Stored procedure '${procedureName}' not found in schema '${schemaToUse}'`);
445
+ }
446
+ const procedure = result.rows[0];
447
+ let definition = procedure.definition;
448
+ try {
449
+ const oidResult = await client.query(
450
+ `
451
+ SELECT p.oid, p.prosrc
452
+ FROM pg_proc p
453
+ JOIN pg_namespace n ON p.pronamespace = n.oid
454
+ WHERE p.proname = $1
455
+ AND n.nspname = $2
456
+ `,
457
+ [procedureName, schemaToUse]
458
+ );
459
+ if (oidResult.rows.length > 0) {
460
+ if (!definition) {
461
+ const oid = oidResult.rows[0].oid;
462
+ const defResult = await client.query(`SELECT pg_get_functiondef($1)`, [oid]);
463
+ if (defResult.rows.length > 0) {
464
+ definition = defResult.rows[0].pg_get_functiondef;
465
+ } else {
466
+ definition = oidResult.rows[0].prosrc;
467
+ }
468
+ }
469
+ }
470
+ } catch (err) {
471
+ console.error(`Error getting procedure definition: ${err}`);
472
+ }
473
+ return {
474
+ procedure_name: procedure.procedure_name,
475
+ procedure_type: procedure.procedure_type,
476
+ language: procedure.language || "sql",
477
+ parameter_list: procedure.parameter_list || "",
478
+ return_type: procedure.return_type !== "void" ? procedure.return_type : void 0,
479
+ definition: definition || void 0
480
+ };
481
+ } finally {
482
+ client.release();
483
+ }
484
+ }
485
+ async executeSQL(sql, options, parameters) {
486
+ if (!this.pool) {
487
+ throw new Error("Not connected to database");
488
+ }
489
+ const client = await this.pool.connect();
490
+ try {
491
+ const statements = splitSQLStatements(sql, "postgres");
492
+ if (statements.length === 1) {
493
+ const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
494
+ if (options.readonly) {
495
+ await client.query("BEGIN READ ONLY");
496
+ try {
497
+ const result2 = parameters && parameters.length > 0 ? await client.query(processedStatement, parameters) : await client.query(processedStatement);
498
+ await client.query("COMMIT");
499
+ return { rows: result2.rows, rowCount: result2.rowCount ?? result2.rows.length };
500
+ } catch (error) {
501
+ try {
502
+ await client.query("ROLLBACK");
503
+ } catch {
504
+ }
505
+ console.error(`[PostgreSQL executeSQL] ERROR: ${error.message}`);
506
+ console.error(`[PostgreSQL executeSQL] SQL: ${processedStatement}`);
507
+ if (parameters && parameters.length > 0) {
508
+ console.error(`[PostgreSQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
509
+ }
510
+ throw error;
511
+ }
512
+ }
513
+ let result;
514
+ if (parameters && parameters.length > 0) {
515
+ try {
516
+ result = await client.query(processedStatement, parameters);
517
+ } catch (error) {
518
+ console.error(`[PostgreSQL executeSQL] ERROR: ${error.message}`);
519
+ console.error(`[PostgreSQL executeSQL] SQL: ${processedStatement}`);
520
+ console.error(`[PostgreSQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
521
+ throw error;
522
+ }
523
+ } else {
524
+ result = await client.query(processedStatement);
525
+ }
526
+ return { rows: result.rows, rowCount: result.rowCount ?? result.rows.length };
527
+ } else {
528
+ if (parameters && parameters.length > 0) {
529
+ throw new Error("Parameters are not supported for multi-statement queries in PostgreSQL");
530
+ }
531
+ let allRows = [];
532
+ let totalRowCount = 0;
533
+ await client.query(options.readonly ? "BEGIN READ ONLY" : "BEGIN");
534
+ try {
535
+ for (let statement of statements) {
536
+ const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
537
+ const result = await client.query(processedStatement);
538
+ if (result.rows && result.rows.length > 0) {
539
+ allRows.push(...result.rows);
540
+ }
541
+ if (result.rowCount) {
542
+ totalRowCount += result.rowCount;
543
+ }
544
+ }
545
+ await client.query("COMMIT");
546
+ } catch (error) {
547
+ try {
548
+ await client.query("ROLLBACK");
549
+ } catch {
550
+ }
551
+ throw error;
552
+ }
553
+ return { rows: allRows, rowCount: totalRowCount };
554
+ }
555
+ } finally {
556
+ client.release();
557
+ }
558
+ }
559
+ };
560
+ var postgresConnector = new PostgresConnector();
561
+ ConnectorRegistry.register(postgresConnector);
562
+ export {
563
+ PostgresConnector
564
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ ToolRegistry,
3
+ getToolRegistry,
4
+ initializeToolRegistry
5
+ } from "./chunk-2A2QF3CS.js";
6
+ import "./chunk-WVVMH6FJ.js";
7
+ import "./chunk-SQA2ISDE.js";
8
+ export {
9
+ ToolRegistry,
10
+ getToolRegistry,
11
+ initializeToolRegistry
12
+ };