@zz1996/dbhub-dameng 0.1.1 → 0.1.3

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.
@@ -5,6 +5,7 @@
5
5
  id = "dm8_dev"
6
6
  description = "Dameng DM8 development database. Read-only access for agent schema discovery and SQL queries."
7
7
  dsn = "dameng://${DM8_USER}:${DM8_PASSWORD}@${DM8_HOST}:5236/${DM8_SCHEMA}"
8
+ connection_timeout = 5
8
9
  query_timeout = 30
9
10
  lazy = true
10
11
 
@@ -23,22 +23,30 @@ Expected: ${expectedFormat}`
23
23
  const url = new SafeURL(dsn);
24
24
  const schema = url.pathname ? decodeURIComponent(url.pathname.substring(1)) : void 0;
25
25
  const port = url.port ? parseInt(url.port, 10) : 5236;
26
- const queryParams = [];
26
+ const queryParams = {};
27
+ const queryStringParts = [];
27
28
  url.forEachSearchParam((value, key) => {
28
- queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
29
+ queryParams[key] = value;
30
+ queryStringParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
29
31
  });
30
- const connectString = `dm://${encodeURIComponent(url.username)}:${encodeURIComponent(url.password)}@${url.hostname}:${port}${queryParams.length > 0 ? `?${queryParams.join("&")}` : ""}`;
31
32
  const connectionConfig = {
32
- connectString,
33
+ connectString: `dm://${encodeURIComponent(url.username)}:${encodeURIComponent(url.password)}@${url.hostname}:${port}${queryStringParts.length > 0 ? `?${queryStringParts.join("&")}` : ""}`,
34
+ directConnectString: `${url.hostname}:${port}`,
33
35
  user: url.username,
34
36
  password: url.password,
35
37
  schema: schema || void 0,
36
38
  poolMin: 0,
37
- poolMax: 4
39
+ poolMax: 4,
40
+ ...queryParams
38
41
  };
39
42
  if (config?.connectionTimeoutSeconds !== void 0) {
43
+ connectionConfig.connectTimeout = config.connectionTimeoutSeconds * 1e3;
40
44
  connectionConfig.queueTimeout = config.connectionTimeoutSeconds * 1e3;
41
45
  }
46
+ if (config?.queryTimeoutSeconds !== void 0) {
47
+ connectionConfig.sessionTimeout = config.queryTimeoutSeconds;
48
+ connectionConfig.socketTimeout = config.queryTimeoutSeconds * 1e3 + 1e3;
49
+ }
42
50
  return connectionConfig;
43
51
  } catch (error) {
44
52
  throw new Error(
@@ -65,6 +73,7 @@ var DamengConnector = class _DamengConnector {
65
73
  this.pool = null;
66
74
  this.sourceId = "default";
67
75
  this.defaultSchema = null;
76
+ this.poolAlias = null;
68
77
  }
69
78
  getId() {
70
79
  return this.sourceId;
@@ -73,13 +82,16 @@ var DamengConnector = class _DamengConnector {
73
82
  return new _DamengConnector();
74
83
  }
75
84
  async connect(dsn, initScript, config) {
85
+ let createdPool = null;
76
86
  try {
77
87
  const connectionConfig = await this.dsnParser.parse(dsn, config);
88
+ connectionConfig.poolAlias = this.buildPoolAlias();
78
89
  this.defaultSchema = connectionConfig.schema ?? null;
79
- if (config?.queryTimeoutSeconds !== void 0) {
80
- this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
81
- }
82
- this.pool = await dmdb.createPool(connectionConfig);
90
+ this.poolAlias = connectionConfig.poolAlias;
91
+ await this.closeRegisteredPool(connectionConfig.poolAlias);
92
+ await this.validateDirectConnection(connectionConfig);
93
+ createdPool = await dmdb.createPool(connectionConfig);
94
+ this.pool = createdPool;
83
95
  await this.withConnection(async (conn) => {
84
96
  await conn.execute("SELECT 1 AS OK", [], this.executeOptions());
85
97
  if (!this.defaultSchema) {
@@ -97,15 +109,26 @@ var DamengConnector = class _DamengConnector {
97
109
  }
98
110
  });
99
111
  } catch (error) {
112
+ if (createdPool) {
113
+ await this.closePoolQuietly(createdPool);
114
+ } else if (this.poolAlias) {
115
+ await this.closeRegisteredPool(this.poolAlias);
116
+ }
117
+ this.pool = null;
118
+ this.poolAlias = null;
100
119
  console.error("Failed to connect to Dameng database:", error);
101
120
  throw error;
102
121
  }
103
122
  }
104
123
  async disconnect() {
105
124
  if (this.pool) {
106
- await this.pool.close(0);
125
+ await this.closePoolQuietly(this.pool);
107
126
  this.pool = null;
108
127
  }
128
+ if (this.poolAlias) {
129
+ await this.closeRegisteredPool(this.poolAlias);
130
+ this.poolAlias = null;
131
+ }
109
132
  }
110
133
  async getSchemas() {
111
134
  const rows = await this.queryRows(`
@@ -116,6 +139,17 @@ var DamengConnector = class _DamengConnector {
116
139
  `);
117
140
  return rows.map((row) => this.rowValue(row, "SCHEMA_NAME")).filter(this.isPresent);
118
141
  }
142
+ async schemaExists(schema) {
143
+ const rows = await this.queryRows(
144
+ `
145
+ SELECT COUNT(*) AS CNT
146
+ FROM ALL_USERS
147
+ WHERE USERNAME = :1
148
+ `,
149
+ [this.normalizeIdentifier(schema)]
150
+ );
151
+ return Number(this.rowValue(rows[0], "CNT") ?? 0) > 0;
152
+ }
119
153
  async getDefaultSchema() {
120
154
  return this.defaultSchema;
121
155
  }
@@ -132,6 +166,25 @@ var DamengConnector = class _DamengConnector {
132
166
  );
133
167
  return rows.map((row) => this.rowValue(row, "TABLE_NAME")).filter(this.isPresent);
134
168
  }
169
+ async searchTables(pattern, schema, limit = 100) {
170
+ const owner = await this.resolveSchema(schema);
171
+ const rowLimit = this.normalizeLimit(limit);
172
+ const rows = await this.queryRows(
173
+ `
174
+ SELECT TABLE_NAME
175
+ FROM (
176
+ SELECT TABLE_NAME
177
+ FROM ALL_TABLES
178
+ WHERE OWNER = :1
179
+ AND TABLE_NAME LIKE :2
180
+ ORDER BY TABLE_NAME
181
+ )
182
+ WHERE ROWNUM <= ${rowLimit}
183
+ `,
184
+ [owner, this.normalizeLikePattern(pattern)]
185
+ );
186
+ return rows.map((row) => this.rowValue(row, "TABLE_NAME")).filter(this.isPresent).map((name) => ({ name, schema: owner }));
187
+ }
135
188
  async getViews(schema) {
136
189
  const owner = await this.resolveSchema(schema);
137
190
  const rows = await this.queryRows(
@@ -362,10 +415,54 @@ var DamengConnector = class _DamengConnector {
362
415
  executeOptions(extra = {}) {
363
416
  return {
364
417
  outFormat: dmdb.OUT_FORMAT_OBJECT,
365
- ...this.queryTimeoutMs !== void 0 && { queryTimeout: this.queryTimeoutMs },
366
418
  ...extra
367
419
  };
368
420
  }
421
+ async validateDirectConnection(config) {
422
+ let conn = null;
423
+ try {
424
+ const {
425
+ directConnectString,
426
+ poolAlias,
427
+ poolMin,
428
+ poolMax,
429
+ queueRequests,
430
+ queueTimeout,
431
+ ...directConfig
432
+ } = config;
433
+ const directConn = await dmdb.getConnection({
434
+ ...directConfig,
435
+ connectString: directConnectString ?? config.connectString
436
+ });
437
+ conn = directConn;
438
+ await conn.execute("SELECT 1 AS OK", [], this.executeOptions());
439
+ } finally {
440
+ if (conn) {
441
+ await conn.close();
442
+ }
443
+ }
444
+ }
445
+ buildPoolAlias() {
446
+ const safeSourceId = this.sourceId.replace(/[^a-zA-Z0-9_-]/g, "_") || "default";
447
+ return `dbhub_dameng_${safeSourceId}`;
448
+ }
449
+ async closeRegisteredPool(poolAlias) {
450
+ if (!dmdb.pools?.has?.(poolAlias)) {
451
+ return;
452
+ }
453
+ const pool = dmdb.pools.get(poolAlias);
454
+ await this.closePoolQuietly(pool);
455
+ dmdb.pools?.delete?.(poolAlias);
456
+ }
457
+ async closePoolQuietly(pool) {
458
+ try {
459
+ await pool.close(0);
460
+ } catch {
461
+ if (pool.poolAlias) {
462
+ dmdb.pools?.delete?.(pool.poolAlias);
463
+ }
464
+ }
465
+ }
369
466
  async resolveSchema(schema) {
370
467
  const resolved = schema || this.defaultSchema || await this.getDefaultSchema();
371
468
  if (!resolved) {
@@ -376,6 +473,12 @@ var DamengConnector = class _DamengConnector {
376
473
  normalizeIdentifier(identifier) {
377
474
  return /[a-z]/.test(identifier) ? identifier.toUpperCase() : identifier;
378
475
  }
476
+ normalizeLikePattern(pattern) {
477
+ return /[a-z]/.test(pattern) ? pattern.toUpperCase() : pattern;
478
+ }
479
+ normalizeLimit(limit) {
480
+ return Math.max(1, Math.min(1e3, Math.floor(limit)));
481
+ }
379
482
  normalizeRows(rows) {
380
483
  return rows.map((row) => {
381
484
  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-DC7OP4VV.js"), name: "Dameng", driver: "dmdb" }
1838
+ { load: () => import("./dameng-JYMTC4HE.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.1",
3
+ "version": "0.1.3",
4
4
  "mcpName": "io.github.zuozh11/dbhub-dameng",
5
5
  "description": "Local fork of DBHub with Dameng/DM8 database connector support",
6
6
  "repository": {