@zz1996/dbhub-dameng 0.1.2 → 0.1.4

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.
@@ -8,6 +8,14 @@ import {
8
8
 
9
9
  // src/connectors/dameng/index.ts
10
10
  import dmdb from "dmdb";
11
+ var DEFAULT_OPERATION_TIMEOUT_MS = 11e4;
12
+ var RESOURCE_CLEANUP_TIMEOUT_MS = 2e3;
13
+ var DamengOperationTimeoutError = class extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "DamengOperationTimeoutError";
17
+ }
18
+ };
11
19
  var DamengDSNParser = class {
12
20
  async parse(dsn, config) {
13
21
  if (!this.isValidDSN(dsn)) {
@@ -23,22 +31,30 @@ Expected: ${expectedFormat}`
23
31
  const url = new SafeURL(dsn);
24
32
  const schema = url.pathname ? decodeURIComponent(url.pathname.substring(1)) : void 0;
25
33
  const port = url.port ? parseInt(url.port, 10) : 5236;
26
- const queryParams = [];
34
+ const queryParams = {};
35
+ const queryStringParts = [];
27
36
  url.forEachSearchParam((value, key) => {
28
- queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
37
+ queryParams[key] = value;
38
+ queryStringParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
29
39
  });
30
- const connectString = `dm://${encodeURIComponent(url.username)}:${encodeURIComponent(url.password)}@${url.hostname}:${port}${queryParams.length > 0 ? `?${queryParams.join("&")}` : ""}`;
31
40
  const connectionConfig = {
32
- connectString,
41
+ connectString: `dm://${encodeURIComponent(url.username)}:${encodeURIComponent(url.password)}@${url.hostname}:${port}${queryStringParts.length > 0 ? `?${queryStringParts.join("&")}` : ""}`,
42
+ directConnectString: `${url.hostname}:${port}`,
33
43
  user: url.username,
34
44
  password: url.password,
35
45
  schema: schema || void 0,
36
46
  poolMin: 0,
37
- poolMax: 4
47
+ poolMax: 4,
48
+ ...queryParams
38
49
  };
39
50
  if (config?.connectionTimeoutSeconds !== void 0) {
51
+ connectionConfig.connectTimeout = config.connectionTimeoutSeconds * 1e3;
40
52
  connectionConfig.queueTimeout = config.connectionTimeoutSeconds * 1e3;
41
53
  }
54
+ if (config?.queryTimeoutSeconds !== void 0) {
55
+ connectionConfig.sessionTimeout = config.queryTimeoutSeconds;
56
+ connectionConfig.socketTimeout = config.queryTimeoutSeconds * 1e3 + 1e3;
57
+ }
42
58
  return connectionConfig;
43
59
  } catch (error) {
44
60
  throw new Error(
@@ -66,6 +82,10 @@ var DamengConnector = class _DamengConnector {
66
82
  this.sourceId = "default";
67
83
  this.defaultSchema = null;
68
84
  this.poolAlias = null;
85
+ this.connectionConfig = null;
86
+ this.connectionTimeoutMs = 5e3;
87
+ this.operationTimeoutMs = DEFAULT_OPERATION_TIMEOUT_MS;
88
+ this.reconnectingPool = null;
69
89
  }
70
90
  getId() {
71
91
  return this.sourceId;
@@ -80,18 +100,22 @@ var DamengConnector = class _DamengConnector {
80
100
  connectionConfig.poolAlias = this.buildPoolAlias();
81
101
  this.defaultSchema = connectionConfig.schema ?? null;
82
102
  this.poolAlias = connectionConfig.poolAlias;
83
- this.queryTimeoutMs = void 0;
84
- if (config?.queryTimeoutSeconds !== void 0) {
85
- this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
86
- }
103
+ this.connectionTimeoutMs = config?.connectionTimeoutSeconds !== void 0 ? config.connectionTimeoutSeconds * 1e3 : 5e3;
104
+ this.operationTimeoutMs = config?.queryTimeoutSeconds !== void 0 ? config.queryTimeoutSeconds * 1e3 : DEFAULT_OPERATION_TIMEOUT_MS;
87
105
  await this.closeRegisteredPool(connectionConfig.poolAlias);
88
106
  await this.validateDirectConnection(connectionConfig);
89
107
  createdPool = await dmdb.createPool(connectionConfig);
90
108
  this.pool = createdPool;
91
109
  await this.withConnection(async (conn) => {
92
- await conn.execute("SELECT 1 AS OK", [], this.executeOptions());
110
+ await this.executeWithTimeout(
111
+ conn,
112
+ "SELECT 1 AS OK",
113
+ [],
114
+ this.executeOptions()
115
+ );
93
116
  if (!this.defaultSchema) {
94
- const result = await conn.execute(
117
+ const result = await this.executeWithTimeout(
118
+ conn,
95
119
  "SELECT USER AS SCHEMA_NAME FROM DUAL",
96
120
  [],
97
121
  this.executeOptions()
@@ -100,10 +124,17 @@ var DamengConnector = class _DamengConnector {
100
124
  }
101
125
  if (initScript) {
102
126
  for (const statement of splitSQLStatements(initScript, "dameng")) {
103
- await conn.execute(statement, [], this.executeOptions({ autoCommit: true }));
127
+ await this.executeWithTimeout(
128
+ conn,
129
+ statement,
130
+ [],
131
+ this.executeOptions({ autoCommit: true })
132
+ );
104
133
  }
105
134
  }
106
135
  });
136
+ this.connectionConfig = connectionConfig;
137
+ this.initScript = initScript;
107
138
  } catch (error) {
108
139
  if (createdPool) {
109
140
  await this.closePoolQuietly(createdPool);
@@ -112,6 +143,8 @@ var DamengConnector = class _DamengConnector {
112
143
  }
113
144
  this.pool = null;
114
145
  this.poolAlias = null;
146
+ this.connectionConfig = null;
147
+ this.initScript = void 0;
115
148
  console.error("Failed to connect to Dameng database:", error);
116
149
  throw error;
117
150
  }
@@ -125,6 +158,9 @@ var DamengConnector = class _DamengConnector {
125
158
  await this.closeRegisteredPool(this.poolAlias);
126
159
  this.poolAlias = null;
127
160
  }
161
+ this.connectionConfig = null;
162
+ this.initScript = void 0;
163
+ this.reconnectingPool = null;
128
164
  }
129
165
  async getSchemas() {
130
166
  const rows = await this.queryRows(`
@@ -135,6 +171,17 @@ var DamengConnector = class _DamengConnector {
135
171
  `);
136
172
  return rows.map((row) => this.rowValue(row, "SCHEMA_NAME")).filter(this.isPresent);
137
173
  }
174
+ async schemaExists(schema) {
175
+ const rows = await this.queryRows(
176
+ `
177
+ SELECT COUNT(*) AS CNT
178
+ FROM ALL_USERS
179
+ WHERE USERNAME = :1
180
+ `,
181
+ [this.normalizeIdentifier(schema)]
182
+ );
183
+ return Number(this.rowValue(rows[0], "CNT") ?? 0) > 0;
184
+ }
138
185
  async getDefaultSchema() {
139
186
  return this.defaultSchema;
140
187
  }
@@ -151,6 +198,25 @@ var DamengConnector = class _DamengConnector {
151
198
  );
152
199
  return rows.map((row) => this.rowValue(row, "TABLE_NAME")).filter(this.isPresent);
153
200
  }
201
+ async searchTables(pattern, schema, limit = 100) {
202
+ const owner = await this.resolveSchema(schema);
203
+ const rowLimit = this.normalizeLimit(limit);
204
+ const rows = await this.queryRows(
205
+ `
206
+ SELECT TABLE_NAME
207
+ FROM (
208
+ SELECT TABLE_NAME
209
+ FROM ALL_TABLES
210
+ WHERE OWNER = :1
211
+ AND TABLE_NAME LIKE :2
212
+ ORDER BY TABLE_NAME
213
+ )
214
+ WHERE ROWNUM <= ${rowLimit}
215
+ `,
216
+ [owner, this.normalizeLikePattern(pattern)]
217
+ );
218
+ return rows.map((row) => this.rowValue(row, "TABLE_NAME")).filter(this.isPresent).map((name) => ({ name, schema: owner }));
219
+ }
154
220
  async getViews(schema) {
155
221
  const owner = await this.resolveSchema(schema);
156
222
  const rows = await this.queryRows(
@@ -345,7 +411,8 @@ var DamengConnector = class _DamengConnector {
345
411
  processedSQL,
346
412
  index === 0 ? parameters ?? [] : []
347
413
  );
348
- const result = await conn.execute(
414
+ const result = await this.executeWithTimeout(
415
+ conn,
349
416
  boundSQL,
350
417
  this.toBindParams(bindValues),
351
418
  this.executeOptions({ autoCommit: true, maxRows: options.maxRows })
@@ -359,40 +426,167 @@ var DamengConnector = class _DamengConnector {
359
426
  }
360
427
  async queryRows(sql, bindValues = []) {
361
428
  return this.withConnection(async (conn) => {
362
- const result = await conn.execute(sql, this.toBindParams(bindValues), this.executeOptions());
429
+ const result = await this.executeWithTimeout(
430
+ conn,
431
+ sql,
432
+ this.toBindParams(bindValues),
433
+ this.executeOptions()
434
+ );
363
435
  return this.normalizeRows(result.rows ?? []);
364
436
  });
365
437
  }
366
438
  async withConnection(fn) {
367
- if (!this.pool) {
439
+ await this.ensurePool();
440
+ const pool = this.pool;
441
+ if (!pool) {
368
442
  throw new Error("Not connected to Dameng database");
369
443
  }
370
- const conn = await this.pool.getConnection();
444
+ let conn = null;
445
+ let shouldRelease = true;
371
446
  try {
447
+ conn = await this.withTimeout(
448
+ pool.getConnection(),
449
+ this.connectionTimeoutMs,
450
+ "Dameng connection acquisition"
451
+ );
372
452
  return await fn(conn);
453
+ } catch (error) {
454
+ if (error instanceof DamengOperationTimeoutError) {
455
+ shouldRelease = false;
456
+ this.markPoolUnhealthy(error.message);
457
+ }
458
+ throw error;
459
+ } finally {
460
+ if (conn && shouldRelease) {
461
+ await this.releaseConnectionQuietly(conn);
462
+ }
463
+ }
464
+ }
465
+ async ensurePool() {
466
+ if (this.pool) {
467
+ return;
468
+ }
469
+ if (!this.connectionConfig) {
470
+ throw new Error("Not connected to Dameng database");
471
+ }
472
+ if (!this.reconnectingPool) {
473
+ this.reconnectingPool = this.reconnectPool();
474
+ }
475
+ try {
476
+ await this.reconnectingPool;
477
+ } finally {
478
+ this.reconnectingPool = null;
479
+ }
480
+ }
481
+ async reconnectPool() {
482
+ const config = this.connectionConfig;
483
+ if (!config) {
484
+ throw new Error("Not connected to Dameng database");
485
+ }
486
+ console.error(`Reconnecting Dameng source '${this.sourceId}' after pool reset...`);
487
+ await this.closeRegisteredPool(config.poolAlias ?? this.buildPoolAlias());
488
+ await this.validateDirectConnection(config);
489
+ const createdPool = await dmdb.createPool(config);
490
+ this.pool = createdPool;
491
+ this.poolAlias = config.poolAlias ?? null;
492
+ try {
493
+ await this.withConnection(async (conn) => {
494
+ await this.executeWithTimeout(conn, "SELECT 1 AS OK", [], this.executeOptions());
495
+ if (this.initScript) {
496
+ for (const statement of splitSQLStatements(this.initScript, "dameng")) {
497
+ await this.executeWithTimeout(
498
+ conn,
499
+ statement,
500
+ [],
501
+ this.executeOptions({ autoCommit: true })
502
+ );
503
+ }
504
+ }
505
+ });
506
+ } catch (error) {
507
+ await this.closePoolQuietly(createdPool);
508
+ this.pool = null;
509
+ throw error;
510
+ }
511
+ }
512
+ executeWithTimeout(conn, sql, bindParams, options) {
513
+ return this.withTimeout(
514
+ conn.execute(sql, bindParams, options),
515
+ this.operationTimeoutMs,
516
+ "Dameng SQL execution"
517
+ );
518
+ }
519
+ async withTimeout(promise, timeoutMs, label) {
520
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
521
+ return promise;
522
+ }
523
+ let timer;
524
+ const timeout = new Promise((_, reject) => {
525
+ timer = setTimeout(() => {
526
+ reject(new DamengOperationTimeoutError(`${label} timed out after ${timeoutMs}ms`));
527
+ }, timeoutMs);
528
+ });
529
+ try {
530
+ return await Promise.race([promise, timeout]);
373
531
  } finally {
374
- if (conn.release) {
375
- await conn.release();
376
- } else {
377
- await conn.close();
532
+ if (timer) {
533
+ clearTimeout(timer);
378
534
  }
379
535
  }
380
536
  }
537
+ markPoolUnhealthy(reason) {
538
+ const pool = this.pool;
539
+ this.pool = null;
540
+ console.error(`Resetting Dameng source '${this.sourceId}' pool: ${reason}`);
541
+ if (pool) {
542
+ void this.closePoolQuietly(pool);
543
+ }
544
+ }
545
+ async releaseConnectionQuietly(conn) {
546
+ try {
547
+ const release = conn.release ? conn.release() : conn.close();
548
+ await this.withTimeout(
549
+ release,
550
+ RESOURCE_CLEANUP_TIMEOUT_MS,
551
+ "Dameng connection release"
552
+ );
553
+ } catch (error) {
554
+ this.markPoolUnhealthy(
555
+ `failed to release connection: ${error instanceof Error ? error.message : String(error)}`
556
+ );
557
+ }
558
+ }
381
559
  executeOptions(extra = {}) {
382
560
  return {
383
561
  outFormat: dmdb.OUT_FORMAT_OBJECT,
384
- ...this.queryTimeoutMs !== void 0 && { queryTimeout: this.queryTimeoutMs },
385
562
  ...extra
386
563
  };
387
564
  }
388
565
  async validateDirectConnection(config) {
389
566
  let conn = null;
390
567
  try {
391
- conn = await dmdb.getConnection(config.connectString);
392
- await conn.execute("SELECT 1 AS OK", [], this.executeOptions());
568
+ const {
569
+ directConnectString,
570
+ poolAlias,
571
+ poolMin,
572
+ poolMax,
573
+ queueRequests,
574
+ queueTimeout,
575
+ ...directConfig
576
+ } = config;
577
+ const directConn = await this.withTimeout(
578
+ dmdb.getConnection({
579
+ ...directConfig,
580
+ connectString: directConnectString ?? config.connectString
581
+ }),
582
+ this.connectionTimeoutMs,
583
+ "Dameng direct connection"
584
+ );
585
+ conn = directConn;
586
+ await this.executeWithTimeout(conn, "SELECT 1 AS OK", [], this.executeOptions());
393
587
  } finally {
394
588
  if (conn) {
395
- await conn.close();
589
+ await this.releaseConnectionQuietly(conn);
396
590
  }
397
591
  }
398
592
  }
@@ -410,7 +604,7 @@ var DamengConnector = class _DamengConnector {
410
604
  }
411
605
  async closePoolQuietly(pool) {
412
606
  try {
413
- await pool.close(0);
607
+ await this.withTimeout(pool.close(0), RESOURCE_CLEANUP_TIMEOUT_MS, "Dameng pool close");
414
608
  } catch {
415
609
  if (pool.poolAlias) {
416
610
  dmdb.pools?.delete?.(pool.poolAlias);
@@ -427,6 +621,12 @@ var DamengConnector = class _DamengConnector {
427
621
  normalizeIdentifier(identifier) {
428
622
  return /[a-z]/.test(identifier) ? identifier.toUpperCase() : identifier;
429
623
  }
624
+ normalizeLikePattern(pattern) {
625
+ return /[a-z]/.test(pattern) ? pattern.toUpperCase() : pattern;
626
+ }
627
+ normalizeLimit(limit) {
628
+ return Math.max(1, Math.min(1e3, Math.floor(limit)));
629
+ }
430
630
  normalizeRows(rows) {
431
631
  return rows.map((row) => {
432
632
  if (!Array.isArray(row)) {
package/dist/index.js CHANGED
@@ -431,23 +431,25 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
431
431
  for (const schemaName of schemasToSearch) {
432
432
  if (results.length >= limit) break;
433
433
  try {
434
- const tables = await connector.getTables(schemaName);
435
- const matched = tables.filter((table) => regex.test(table));
436
- for (const tableName of matched) {
434
+ const searchableConnector = connector;
435
+ const matched = searchableConnector.searchTables ? await searchableConnector.searchTables(pattern, schemaName, limit - results.length) : (await connector.getTables(schemaName)).filter((table) => regex.test(table)).map((name) => ({ name, schema: schemaName }));
436
+ for (const tableMatch of matched) {
437
437
  if (results.length >= limit) break;
438
+ const tableName = tableMatch.name;
439
+ const matchedSchema = tableMatch.schema;
438
440
  if (detailLevel === "names") {
439
441
  results.push({
440
442
  name: tableName,
441
- schema: schemaName
443
+ schema: matchedSchema
442
444
  });
443
445
  } else if (detailLevel === "summary") {
444
446
  try {
445
- const columns = await connector.getTableSchema(tableName, schemaName);
446
- const rowCount = await getTableRowCount(connector, tableName, schemaName);
447
- const comment = await getTableComment(connector, tableName, schemaName);
447
+ const columns = await connector.getTableSchema(tableName, matchedSchema);
448
+ const rowCount = await getTableRowCount(connector, tableName, matchedSchema);
449
+ const comment = await getTableComment(connector, tableName, matchedSchema);
448
450
  results.push({
449
451
  name: tableName,
450
- schema: schemaName,
452
+ schema: matchedSchema,
451
453
  column_count: columns.length,
452
454
  row_count: rowCount,
453
455
  ...comment ? { comment } : {}
@@ -455,20 +457,20 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
455
457
  } catch (error) {
456
458
  results.push({
457
459
  name: tableName,
458
- schema: schemaName,
460
+ schema: matchedSchema,
459
461
  column_count: null,
460
462
  row_count: null
461
463
  });
462
464
  }
463
465
  } else {
464
466
  try {
465
- const columns = await connector.getTableSchema(tableName, schemaName);
466
- const indexes = await connector.getTableIndexes(tableName, schemaName);
467
- const rowCount = await getTableRowCount(connector, tableName, schemaName);
468
- const comment = await getTableComment(connector, tableName, schemaName);
467
+ const columns = await connector.getTableSchema(tableName, matchedSchema);
468
+ const indexes = await connector.getTableIndexes(tableName, matchedSchema);
469
+ const rowCount = await getTableRowCount(connector, tableName, matchedSchema);
470
+ const comment = await getTableComment(connector, tableName, matchedSchema);
469
471
  results.push({
470
472
  name: tableName,
471
- schema: schemaName,
473
+ schema: matchedSchema,
472
474
  column_count: columns.length,
473
475
  row_count: rowCount,
474
476
  ...comment ? { comment } : {},
@@ -489,7 +491,7 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
489
491
  } catch (error) {
490
492
  results.push({
491
493
  name: tableName,
492
- schema: schemaName,
494
+ schema: matchedSchema,
493
495
  error: `Unable to fetch full details: ${error.message}`
494
496
  });
495
497
  }
@@ -759,10 +761,10 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
759
761
  }
760
762
  }
761
763
  if (schema) {
762
- const schemas = await connector.getSchemas();
763
- if (!schemas.includes(schema)) {
764
+ const exists = connector.schemaExists ? await connector.schemaExists(schema) : (await connector.getSchemas()).includes(schema);
765
+ if (!exists) {
764
766
  success = false;
765
- errorMessage = `Schema '${schema}' does not exist. Available schemas: ${schemas.join(", ")}`;
767
+ errorMessage = `Schema '${schema}' does not exist`;
766
768
  return createToolErrorResponse(errorMessage, "SCHEMA_NOT_FOUND");
767
769
  }
768
770
  }
@@ -1833,7 +1835,7 @@ var connectorModules = [
1833
1835
  { load: () => import("./sqlite-IOUAYHGE.js"), name: "SQLite", driver: "node:sqlite" },
1834
1836
  { load: () => import("./mysql-A43SL7UM.js"), name: "MySQL", driver: "mysql2" },
1835
1837
  { load: () => import("./mariadb-7F72IRB4.js"), name: "MariaDB", driver: "mariadb" },
1836
- { load: () => import("./dameng-UJELBZ5R.js"), name: "Dameng", driver: "dmdb" }
1838
+ { load: () => import("./dameng-SOGGZUEW.js"), name: "Dameng", driver: "dmdb" }
1837
1839
  ];
1838
1840
  loadConnectors(connectorModules).then(() => main()).catch((error) => {
1839
1841
  console.error("Fatal error:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zz1996/dbhub-dameng",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "mcpName": "io.github.zuozh11/dbhub-dameng",
5
5
  "description": "Local fork of DBHub with Dameng/DM8 database connector support",
6
6
  "repository": {