@zz1996/dbhub-dameng 0.1.3 → 0.1.5

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)) {
@@ -74,6 +82,10 @@ var DamengConnector = class _DamengConnector {
74
82
  this.sourceId = "default";
75
83
  this.defaultSchema = null;
76
84
  this.poolAlias = null;
85
+ this.connectionConfig = null;
86
+ this.connectionTimeoutMs = 5e3;
87
+ this.operationTimeoutMs = DEFAULT_OPERATION_TIMEOUT_MS;
88
+ this.reconnectingPool = null;
77
89
  }
78
90
  getId() {
79
91
  return this.sourceId;
@@ -88,14 +100,22 @@ var DamengConnector = class _DamengConnector {
88
100
  connectionConfig.poolAlias = this.buildPoolAlias();
89
101
  this.defaultSchema = connectionConfig.schema ?? null;
90
102
  this.poolAlias = connectionConfig.poolAlias;
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;
91
105
  await this.closeRegisteredPool(connectionConfig.poolAlias);
92
106
  await this.validateDirectConnection(connectionConfig);
93
107
  createdPool = await dmdb.createPool(connectionConfig);
94
108
  this.pool = createdPool;
95
109
  await this.withConnection(async (conn) => {
96
- 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
+ );
97
116
  if (!this.defaultSchema) {
98
- const result = await conn.execute(
117
+ const result = await this.executeWithTimeout(
118
+ conn,
99
119
  "SELECT USER AS SCHEMA_NAME FROM DUAL",
100
120
  [],
101
121
  this.executeOptions()
@@ -104,10 +124,17 @@ var DamengConnector = class _DamengConnector {
104
124
  }
105
125
  if (initScript) {
106
126
  for (const statement of splitSQLStatements(initScript, "dameng")) {
107
- await conn.execute(statement, [], this.executeOptions({ autoCommit: true }));
127
+ await this.executeWithTimeout(
128
+ conn,
129
+ statement,
130
+ [],
131
+ this.executeOptions({ autoCommit: true })
132
+ );
108
133
  }
109
134
  }
110
135
  });
136
+ this.connectionConfig = connectionConfig;
137
+ this.initScript = initScript;
111
138
  } catch (error) {
112
139
  if (createdPool) {
113
140
  await this.closePoolQuietly(createdPool);
@@ -116,6 +143,8 @@ var DamengConnector = class _DamengConnector {
116
143
  }
117
144
  this.pool = null;
118
145
  this.poolAlias = null;
146
+ this.connectionConfig = null;
147
+ this.initScript = void 0;
119
148
  console.error("Failed to connect to Dameng database:", error);
120
149
  throw error;
121
150
  }
@@ -129,6 +158,9 @@ var DamengConnector = class _DamengConnector {
129
158
  await this.closeRegisteredPool(this.poolAlias);
130
159
  this.poolAlias = null;
131
160
  }
161
+ this.connectionConfig = null;
162
+ this.initScript = void 0;
163
+ this.reconnectingPool = null;
132
164
  }
133
165
  async getSchemas() {
134
166
  const rows = await this.queryRows(`
@@ -185,6 +217,64 @@ var DamengConnector = class _DamengConnector {
185
217
  );
186
218
  return rows.map((row) => this.rowValue(row, "TABLE_NAME")).filter(this.isPresent).map((name) => ({ name, schema: owner }));
187
219
  }
220
+ async searchColumns(pattern, schema, table, limit = 100) {
221
+ const owner = await this.resolveSchema(schema);
222
+ const rowLimit = this.normalizeLimit(limit);
223
+ const bindValues = [
224
+ owner,
225
+ this.normalizeLikePattern(pattern),
226
+ ...table ? [this.normalizeIdentifier(table)] : []
227
+ ];
228
+ const tablePredicate = table ? "AND c.TABLE_NAME = :3" : "";
229
+ const rows = await this.queryRows(
230
+ `
231
+ SELECT COLUMN_NAME,
232
+ TABLE_NAME,
233
+ DATA_TYPE,
234
+ DATA_LENGTH,
235
+ DATA_PRECISION,
236
+ DATA_SCALE,
237
+ NULLABLE,
238
+ DATA_DEFAULT,
239
+ COMMENTS
240
+ FROM (
241
+ SELECT c.COLUMN_NAME,
242
+ c.TABLE_NAME,
243
+ c.DATA_TYPE,
244
+ c.DATA_LENGTH,
245
+ c.DATA_PRECISION,
246
+ c.DATA_SCALE,
247
+ c.NULLABLE,
248
+ c.DATA_DEFAULT,
249
+ cc.COMMENTS,
250
+ c.COLUMN_ID
251
+ FROM ALL_TAB_COLUMNS c
252
+ LEFT JOIN ALL_COL_COMMENTS cc
253
+ ON cc.OWNER = c.OWNER
254
+ AND cc.TABLE_NAME = c.TABLE_NAME
255
+ AND cc.COLUMN_NAME = c.COLUMN_NAME
256
+ WHERE c.OWNER = :1
257
+ AND c.COLUMN_NAME LIKE :2
258
+ ${tablePredicate}
259
+ ORDER BY c.TABLE_NAME, c.COLUMN_ID
260
+ )
261
+ WHERE ROWNUM <= ${rowLimit}
262
+ `,
263
+ bindValues
264
+ );
265
+ return rows.map((row) => {
266
+ const description = this.rowValue(row, "COMMENTS");
267
+ return {
268
+ name: this.rowValue(row, "COLUMN_NAME") ?? "",
269
+ table: this.rowValue(row, "TABLE_NAME") ?? "",
270
+ schema: owner,
271
+ type: this.formatDataType(row),
272
+ nullable: this.rowValue(row, "NULLABLE") === "Y",
273
+ default: this.rowValue(row, "DATA_DEFAULT") ?? null,
274
+ ...description ? { description } : {}
275
+ };
276
+ });
277
+ }
188
278
  async getViews(schema) {
189
279
  const owner = await this.resolveSchema(schema);
190
280
  const rows = await this.queryRows(
@@ -379,7 +469,8 @@ var DamengConnector = class _DamengConnector {
379
469
  processedSQL,
380
470
  index === 0 ? parameters ?? [] : []
381
471
  );
382
- const result = await conn.execute(
472
+ const result = await this.executeWithTimeout(
473
+ conn,
383
474
  boundSQL,
384
475
  this.toBindParams(bindValues),
385
476
  this.executeOptions({ autoCommit: true, maxRows: options.maxRows })
@@ -393,25 +484,136 @@ var DamengConnector = class _DamengConnector {
393
484
  }
394
485
  async queryRows(sql, bindValues = []) {
395
486
  return this.withConnection(async (conn) => {
396
- const result = await conn.execute(sql, this.toBindParams(bindValues), this.executeOptions());
487
+ const result = await this.executeWithTimeout(
488
+ conn,
489
+ sql,
490
+ this.toBindParams(bindValues),
491
+ this.executeOptions()
492
+ );
397
493
  return this.normalizeRows(result.rows ?? []);
398
494
  });
399
495
  }
400
496
  async withConnection(fn) {
401
- if (!this.pool) {
497
+ await this.ensurePool();
498
+ const pool = this.pool;
499
+ if (!pool) {
402
500
  throw new Error("Not connected to Dameng database");
403
501
  }
404
- const conn = await this.pool.getConnection();
502
+ let conn = null;
503
+ let shouldRelease = true;
405
504
  try {
505
+ conn = await this.withTimeout(
506
+ pool.getConnection(),
507
+ this.connectionTimeoutMs,
508
+ "Dameng connection acquisition"
509
+ );
406
510
  return await fn(conn);
511
+ } catch (error) {
512
+ if (error instanceof DamengOperationTimeoutError) {
513
+ shouldRelease = false;
514
+ this.markPoolUnhealthy(error.message);
515
+ }
516
+ throw error;
517
+ } finally {
518
+ if (conn && shouldRelease) {
519
+ await this.releaseConnectionQuietly(conn);
520
+ }
521
+ }
522
+ }
523
+ async ensurePool() {
524
+ if (this.pool) {
525
+ return;
526
+ }
527
+ if (!this.connectionConfig) {
528
+ throw new Error("Not connected to Dameng database");
529
+ }
530
+ if (!this.reconnectingPool) {
531
+ this.reconnectingPool = this.reconnectPool();
532
+ }
533
+ try {
534
+ await this.reconnectingPool;
407
535
  } finally {
408
- if (conn.release) {
409
- await conn.release();
410
- } else {
411
- await conn.close();
536
+ this.reconnectingPool = null;
537
+ }
538
+ }
539
+ async reconnectPool() {
540
+ const config = this.connectionConfig;
541
+ if (!config) {
542
+ throw new Error("Not connected to Dameng database");
543
+ }
544
+ console.error(`Reconnecting Dameng source '${this.sourceId}' after pool reset...`);
545
+ await this.closeRegisteredPool(config.poolAlias ?? this.buildPoolAlias());
546
+ await this.validateDirectConnection(config);
547
+ const createdPool = await dmdb.createPool(config);
548
+ this.pool = createdPool;
549
+ this.poolAlias = config.poolAlias ?? null;
550
+ try {
551
+ await this.withConnection(async (conn) => {
552
+ await this.executeWithTimeout(conn, "SELECT 1 AS OK", [], this.executeOptions());
553
+ if (this.initScript) {
554
+ for (const statement of splitSQLStatements(this.initScript, "dameng")) {
555
+ await this.executeWithTimeout(
556
+ conn,
557
+ statement,
558
+ [],
559
+ this.executeOptions({ autoCommit: true })
560
+ );
561
+ }
562
+ }
563
+ });
564
+ } catch (error) {
565
+ await this.closePoolQuietly(createdPool);
566
+ this.pool = null;
567
+ throw error;
568
+ }
569
+ }
570
+ executeWithTimeout(conn, sql, bindParams, options) {
571
+ return this.withTimeout(
572
+ conn.execute(sql, bindParams, options),
573
+ this.operationTimeoutMs,
574
+ "Dameng SQL execution"
575
+ );
576
+ }
577
+ async withTimeout(promise, timeoutMs, label) {
578
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
579
+ return promise;
580
+ }
581
+ let timer;
582
+ const timeout = new Promise((_, reject) => {
583
+ timer = setTimeout(() => {
584
+ reject(new DamengOperationTimeoutError(`${label} timed out after ${timeoutMs}ms`));
585
+ }, timeoutMs);
586
+ });
587
+ try {
588
+ return await Promise.race([promise, timeout]);
589
+ } finally {
590
+ if (timer) {
591
+ clearTimeout(timer);
412
592
  }
413
593
  }
414
594
  }
595
+ markPoolUnhealthy(reason) {
596
+ const pool = this.pool;
597
+ this.pool = null;
598
+ console.error(`Resetting Dameng source '${this.sourceId}' pool: ${reason}`);
599
+ if (pool) {
600
+ void this.closePoolQuietly(pool);
601
+ }
602
+ }
603
+ async releaseConnectionQuietly(conn) {
604
+ try {
605
+ const release = conn.release ? conn.release() : conn.close();
606
+ await this.withTimeout(
607
+ release,
608
+ RESOURCE_CLEANUP_TIMEOUT_MS,
609
+ "Dameng connection release"
610
+ );
611
+ } catch (error) {
612
+ this.markPoolUnhealthy(
613
+ `failed to release connection: ${error instanceof Error ? error.message : String(error)}`
614
+ );
615
+ }
616
+ }
415
617
  executeOptions(extra = {}) {
416
618
  return {
417
619
  outFormat: dmdb.OUT_FORMAT_OBJECT,
@@ -430,15 +632,19 @@ var DamengConnector = class _DamengConnector {
430
632
  queueTimeout,
431
633
  ...directConfig
432
634
  } = config;
433
- const directConn = await dmdb.getConnection({
434
- ...directConfig,
435
- connectString: directConnectString ?? config.connectString
436
- });
635
+ const directConn = await this.withTimeout(
636
+ dmdb.getConnection({
637
+ ...directConfig,
638
+ connectString: directConnectString ?? config.connectString
639
+ }),
640
+ this.connectionTimeoutMs,
641
+ "Dameng direct connection"
642
+ );
437
643
  conn = directConn;
438
- await conn.execute("SELECT 1 AS OK", [], this.executeOptions());
644
+ await this.executeWithTimeout(conn, "SELECT 1 AS OK", [], this.executeOptions());
439
645
  } finally {
440
646
  if (conn) {
441
- await conn.close();
647
+ await this.releaseConnectionQuietly(conn);
442
648
  }
443
649
  }
444
650
  }
@@ -456,7 +662,7 @@ var DamengConnector = class _DamengConnector {
456
662
  }
457
663
  async closePoolQuietly(pool) {
458
664
  try {
459
- await pool.close(0);
665
+ await this.withTimeout(pool.close(0), RESOURCE_CLEANUP_TIMEOUT_MS, "Dameng pool close");
460
666
  } catch {
461
667
  if (pool.poolAlias) {
462
668
  dmdb.pools?.delete?.(pool.poolAlias);
package/dist/index.js CHANGED
@@ -586,6 +586,36 @@ async function searchColumns(connector, pattern, schemaFilter, tableFilter, deta
586
586
  for (const schemaName of schemasToSearch) {
587
587
  if (results.length >= limit) break;
588
588
  try {
589
+ const searchableConnector = connector;
590
+ if (searchableConnector.searchColumns) {
591
+ const matchedColumns = await searchableConnector.searchColumns(
592
+ pattern,
593
+ schemaName,
594
+ tableFilter,
595
+ limit - results.length
596
+ );
597
+ for (const column of matchedColumns) {
598
+ if (results.length >= limit) break;
599
+ if (detailLevel === "names") {
600
+ results.push({
601
+ name: column.name,
602
+ table: column.table,
603
+ schema: column.schema
604
+ });
605
+ } else {
606
+ results.push({
607
+ name: column.name,
608
+ table: column.table,
609
+ schema: column.schema,
610
+ type: column.type,
611
+ nullable: column.nullable,
612
+ default: column.default,
613
+ ...column.description ? { description: column.description } : {}
614
+ });
615
+ }
616
+ }
617
+ continue;
618
+ }
589
619
  let tablesToSearch;
590
620
  if (tableFilter) {
591
621
  tablesToSearch = [tableFilter];
@@ -1835,7 +1865,7 @@ var connectorModules = [
1835
1865
  { load: () => import("./sqlite-IOUAYHGE.js"), name: "SQLite", driver: "node:sqlite" },
1836
1866
  { load: () => import("./mysql-A43SL7UM.js"), name: "MySQL", driver: "mysql2" },
1837
1867
  { load: () => import("./mariadb-7F72IRB4.js"), name: "MariaDB", driver: "mariadb" },
1838
- { load: () => import("./dameng-JYMTC4HE.js"), name: "Dameng", driver: "dmdb" }
1868
+ { load: () => import("./dameng-DTI47UV6.js"), name: "Dameng", driver: "dmdb" }
1839
1869
  ];
1840
1870
  loadConnectors(connectorModules).then(() => main()).catch((error) => {
1841
1871
  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.3",
3
+ "version": "0.1.5",
4
4
  "mcpName": "io.github.zuozh11/dbhub-dameng",
5
5
  "description": "Local fork of DBHub with Dameng/DM8 database connector support",
6
6
  "repository": {