@bytebase/dbhub 0.20.0 → 0.21.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.
@@ -898,7 +898,7 @@ async function resolveSourceConfigs() {
898
898
  source.ssh_keepalive_count_max = sshResult.config.keepaliveCountMax;
899
899
  }
900
900
  if (dsnResult.isDemo) {
901
- const { getSqliteInMemorySetupSql } = await import("./demo-loader-FM5OJVDA.js");
901
+ const { getSqliteInMemorySetupSql } = await import("./demo-loader-PSMTLZ2T.js");
902
902
  source.init_script = getSqliteInMemorySetupSql();
903
903
  }
904
904
  return {
@@ -0,0 +1,60 @@
1
+ // src/utils/multi-statement-result-parser.ts
2
+ function isMetadataObject(element) {
3
+ if (!element || typeof element !== "object" || Array.isArray(element)) {
4
+ return false;
5
+ }
6
+ return "affectedRows" in element || "insertId" in element || "fieldCount" in element || "warningStatus" in element;
7
+ }
8
+ function isMultiStatementResult(results) {
9
+ if (!Array.isArray(results) || results.length === 0) {
10
+ return false;
11
+ }
12
+ const firstElement = results[0];
13
+ return isMetadataObject(firstElement) || Array.isArray(firstElement);
14
+ }
15
+ function extractRowsFromMultiStatement(results) {
16
+ if (!Array.isArray(results)) {
17
+ return [];
18
+ }
19
+ const allRows = [];
20
+ for (const result of results) {
21
+ if (Array.isArray(result)) {
22
+ allRows.push(...result);
23
+ }
24
+ }
25
+ return allRows;
26
+ }
27
+ function extractAffectedRows(results) {
28
+ if (isMetadataObject(results)) {
29
+ return results.affectedRows || 0;
30
+ }
31
+ if (!Array.isArray(results)) {
32
+ return 0;
33
+ }
34
+ if (isMultiStatementResult(results)) {
35
+ let totalAffected = 0;
36
+ for (const result of results) {
37
+ if (isMetadataObject(result)) {
38
+ totalAffected += result.affectedRows || 0;
39
+ } else if (Array.isArray(result)) {
40
+ totalAffected += result.length;
41
+ }
42
+ }
43
+ return totalAffected;
44
+ }
45
+ return results.length;
46
+ }
47
+ function parseQueryResults(results) {
48
+ if (!Array.isArray(results)) {
49
+ return [];
50
+ }
51
+ if (isMultiStatementResult(results)) {
52
+ return extractRowsFromMultiStatement(results);
53
+ }
54
+ return results;
55
+ }
56
+
57
+ export {
58
+ extractAffectedRows,
59
+ parseQueryResults
60
+ };
@@ -1,5 +1,3 @@
1
- import "./chunk-WWAWV7DQ.js";
2
-
3
1
  // src/config/demo-loader.ts
4
2
  import fs from "fs";
5
3
  import path from "path";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  resolveSourceConfigs,
13
13
  resolveTomlConfigPath,
14
14
  resolveTransport
15
- } from "./chunk-25VMLRAQ.js";
15
+ } from "./chunk-GRGEI5QT.js";
16
16
  import {
17
17
  quoteQualifiedIdentifier
18
18
  } from "./chunk-JFWX35TB.js";
@@ -24,7 +24,6 @@ import {
24
24
  splitSQLStatements,
25
25
  stripCommentsAndStrings
26
26
  } from "./chunk-C7WEAPX4.js";
27
- import "./chunk-WWAWV7DQ.js";
28
27
 
29
28
  // src/server.ts
30
29
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -1363,7 +1362,7 @@ See documentation for more details on configuring database connections.
1363
1362
  const sources = sourceConfigsData.sources;
1364
1363
  console.error(`Configuration source: ${sourceConfigsData.source}`);
1365
1364
  await connectorManager.connectWithSources(sources);
1366
- const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-FOASCI6Y.js");
1365
+ const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-XSMMLRC4.js");
1367
1366
  initializeToolRegistry2({
1368
1367
  sources: sourceConfigsData.sources,
1369
1368
  tools: sourceConfigsData.tools
@@ -1522,11 +1521,11 @@ async function loadConnectors(connectorModules2) {
1522
1521
 
1523
1522
  // src/index.ts
1524
1523
  var connectorModules = [
1525
- { load: () => import("./postgres-JB3LPXGR.js"), name: "PostgreSQL", driver: "pg" },
1526
- { load: () => import("./sqlserver-LGFLHJHL.js"), name: "SQL Server", driver: "mssql" },
1527
- { load: () => import("./sqlite-5LT56F5B.js"), name: "SQLite", driver: "better-sqlite3" },
1528
- { load: () => import("./mysql-FOCVUTPX.js"), name: "MySQL", driver: "mysql2" },
1529
- { load: () => import("./mariadb-L3YMONWJ.js"), name: "MariaDB", driver: "mariadb" }
1524
+ { load: () => import("./postgres-B7YSSZMH.js"), name: "PostgreSQL", driver: "pg" },
1525
+ { load: () => import("./sqlserver-FDBRUELV.js"), name: "SQL Server", driver: "mssql" },
1526
+ { load: () => import("./sqlite-FSCLCRIH.js"), name: "SQLite", driver: "better-sqlite3" },
1527
+ { load: () => import("./mysql-I35IQ2GH.js"), name: "MySQL", driver: "mysql2" },
1528
+ { load: () => import("./mariadb-VGTS4WXE.js"), name: "MariaDB", driver: "mariadb" }
1530
1529
  ];
1531
1530
  loadConnectors(connectorModules).then(() => main()).catch((error) => {
1532
1531
  console.error("Fatal error:", error);
@@ -0,0 +1,462 @@
1
+ import {
2
+ extractAffectedRows,
3
+ parseQueryResults
4
+ } from "./chunk-RTB262PR.js";
5
+ import {
6
+ SQLRowLimiter
7
+ } from "./chunk-BRXZ5ZQB.js";
8
+ import {
9
+ ConnectorRegistry,
10
+ SafeURL,
11
+ obfuscateDSNPassword,
12
+ splitSQLStatements
13
+ } from "./chunk-C7WEAPX4.js";
14
+
15
+ // src/connectors/mariadb/index.ts
16
+ import * as mariadb from "mariadb";
17
+ var MariadbDSNParser = class {
18
+ async parse(dsn, config) {
19
+ const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
20
+ const queryTimeoutSeconds = config?.queryTimeoutSeconds;
21
+ if (!this.isValidDSN(dsn)) {
22
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
23
+ const expectedFormat = this.getSampleDSN();
24
+ throw new Error(
25
+ `Invalid MariaDB DSN format.
26
+ Provided: ${obfuscatedDSN}
27
+ Expected: ${expectedFormat}`
28
+ );
29
+ }
30
+ try {
31
+ const url = new SafeURL(dsn);
32
+ const connectionConfig = {
33
+ host: url.hostname,
34
+ port: url.port ? parseInt(url.port) : 3306,
35
+ database: url.pathname ? url.pathname.substring(1) : "",
36
+ // Remove leading '/' if exists
37
+ user: url.username,
38
+ password: url.password,
39
+ multipleStatements: true,
40
+ // Enable native multi-statement support
41
+ ...connectionTimeoutSeconds !== void 0 && {
42
+ connectTimeout: connectionTimeoutSeconds * 1e3
43
+ },
44
+ ...queryTimeoutSeconds !== void 0 && {
45
+ queryTimeout: queryTimeoutSeconds * 1e3
46
+ }
47
+ };
48
+ url.forEachSearchParam((value, key) => {
49
+ if (key === "sslmode") {
50
+ if (value === "disable") {
51
+ connectionConfig.ssl = void 0;
52
+ } else if (value === "require") {
53
+ connectionConfig.ssl = { rejectUnauthorized: false };
54
+ } else {
55
+ connectionConfig.ssl = {};
56
+ }
57
+ }
58
+ });
59
+ if (url.password && url.password.includes("X-Amz-Credential")) {
60
+ if (connectionConfig.ssl === void 0) {
61
+ connectionConfig.ssl = { rejectUnauthorized: false };
62
+ }
63
+ }
64
+ return connectionConfig;
65
+ } catch (error) {
66
+ throw new Error(
67
+ `Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`
68
+ );
69
+ }
70
+ }
71
+ getSampleDSN() {
72
+ return "mariadb://root:password@localhost:3306/db?sslmode=require";
73
+ }
74
+ isValidDSN(dsn) {
75
+ try {
76
+ return dsn.startsWith("mariadb://");
77
+ } catch (error) {
78
+ return false;
79
+ }
80
+ }
81
+ };
82
+ var MariaDBConnector = class _MariaDBConnector {
83
+ constructor() {
84
+ this.id = "mariadb";
85
+ this.name = "MariaDB";
86
+ this.dsnParser = new MariadbDSNParser();
87
+ this.pool = null;
88
+ // Source ID is set by ConnectorManager after cloning
89
+ this.sourceId = "default";
90
+ }
91
+ getId() {
92
+ return this.sourceId;
93
+ }
94
+ clone() {
95
+ return new _MariaDBConnector();
96
+ }
97
+ async connect(dsn, initScript, config) {
98
+ try {
99
+ const connectionConfig = await this.dsnParser.parse(dsn, config);
100
+ this.pool = mariadb.createPool(connectionConfig);
101
+ await this.pool.query("SELECT 1");
102
+ } catch (err) {
103
+ console.error("Failed to connect to MariaDB database:", err);
104
+ throw err;
105
+ }
106
+ }
107
+ async disconnect() {
108
+ if (this.pool) {
109
+ await this.pool.end();
110
+ this.pool = null;
111
+ }
112
+ }
113
+ async getSchemas() {
114
+ if (!this.pool) {
115
+ throw new Error("Not connected to database");
116
+ }
117
+ try {
118
+ const rows = await this.pool.query(`
119
+ SELECT SCHEMA_NAME
120
+ FROM INFORMATION_SCHEMA.SCHEMATA
121
+ ORDER BY SCHEMA_NAME
122
+ `);
123
+ return rows.map((row) => row.SCHEMA_NAME);
124
+ } catch (error) {
125
+ console.error("Error getting schemas:", error);
126
+ throw error;
127
+ }
128
+ }
129
+ async getTables(schema) {
130
+ if (!this.pool) {
131
+ throw new Error("Not connected to database");
132
+ }
133
+ try {
134
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
135
+ const queryParams = schema ? [schema] : [];
136
+ const rows = await this.pool.query(
137
+ `
138
+ SELECT TABLE_NAME
139
+ FROM INFORMATION_SCHEMA.TABLES
140
+ ${schemaClause}
141
+ ORDER BY TABLE_NAME
142
+ `,
143
+ queryParams
144
+ );
145
+ return rows.map((row) => row.TABLE_NAME);
146
+ } catch (error) {
147
+ console.error("Error getting tables:", error);
148
+ throw error;
149
+ }
150
+ }
151
+ async tableExists(tableName, schema) {
152
+ if (!this.pool) {
153
+ throw new Error("Not connected to database");
154
+ }
155
+ try {
156
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
157
+ const queryParams = schema ? [schema, tableName] : [tableName];
158
+ const rows = await this.pool.query(
159
+ `
160
+ SELECT COUNT(*) AS COUNT
161
+ FROM INFORMATION_SCHEMA.TABLES
162
+ ${schemaClause}
163
+ AND TABLE_NAME = ?
164
+ `,
165
+ queryParams
166
+ );
167
+ return rows[0].COUNT > 0;
168
+ } catch (error) {
169
+ console.error("Error checking if table exists:", error);
170
+ throw error;
171
+ }
172
+ }
173
+ async getTableIndexes(tableName, schema) {
174
+ if (!this.pool) {
175
+ throw new Error("Not connected to database");
176
+ }
177
+ try {
178
+ const schemaClause = schema ? "TABLE_SCHEMA = ?" : "TABLE_SCHEMA = DATABASE()";
179
+ const queryParams = schema ? [schema, tableName] : [tableName];
180
+ const indexRows = await this.pool.query(
181
+ `
182
+ SELECT
183
+ INDEX_NAME,
184
+ COLUMN_NAME,
185
+ NON_UNIQUE,
186
+ SEQ_IN_INDEX
187
+ FROM
188
+ INFORMATION_SCHEMA.STATISTICS
189
+ WHERE
190
+ ${schemaClause}
191
+ AND TABLE_NAME = ?
192
+ ORDER BY
193
+ INDEX_NAME,
194
+ SEQ_IN_INDEX
195
+ `,
196
+ queryParams
197
+ );
198
+ const indexMap = /* @__PURE__ */ new Map();
199
+ for (const row of indexRows) {
200
+ const indexName = row.INDEX_NAME;
201
+ const columnName = row.COLUMN_NAME;
202
+ const isUnique = row.NON_UNIQUE === 0;
203
+ const isPrimary = indexName === "PRIMARY";
204
+ if (!indexMap.has(indexName)) {
205
+ indexMap.set(indexName, {
206
+ columns: [],
207
+ is_unique: isUnique,
208
+ is_primary: isPrimary
209
+ });
210
+ }
211
+ const indexInfo = indexMap.get(indexName);
212
+ indexInfo.columns.push(columnName);
213
+ }
214
+ const results = [];
215
+ indexMap.forEach((indexInfo, indexName) => {
216
+ results.push({
217
+ index_name: indexName,
218
+ column_names: indexInfo.columns,
219
+ is_unique: indexInfo.is_unique,
220
+ is_primary: indexInfo.is_primary
221
+ });
222
+ });
223
+ return results;
224
+ } catch (error) {
225
+ console.error("Error getting table indexes:", error);
226
+ throw error;
227
+ }
228
+ }
229
+ async getTableSchema(tableName, schema) {
230
+ if (!this.pool) {
231
+ throw new Error("Not connected to database");
232
+ }
233
+ try {
234
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
235
+ const queryParams = schema ? [schema, tableName] : [tableName];
236
+ const rows = await this.pool.query(
237
+ `
238
+ SELECT
239
+ COLUMN_NAME as column_name,
240
+ DATA_TYPE as data_type,
241
+ IS_NULLABLE as is_nullable,
242
+ COLUMN_DEFAULT as column_default,
243
+ COLUMN_COMMENT as description
244
+ FROM INFORMATION_SCHEMA.COLUMNS
245
+ ${schemaClause}
246
+ AND TABLE_NAME = ?
247
+ ORDER BY ORDINAL_POSITION
248
+ `,
249
+ queryParams
250
+ );
251
+ return rows.map((row) => ({
252
+ ...row,
253
+ description: row.description || null
254
+ }));
255
+ } catch (error) {
256
+ console.error("Error getting table schema:", error);
257
+ throw error;
258
+ }
259
+ }
260
+ async getTableComment(tableName, schema) {
261
+ if (!this.pool) {
262
+ throw new Error("Not connected to database");
263
+ }
264
+ try {
265
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
266
+ const queryParams = schema ? [schema, tableName] : [tableName];
267
+ const rows = await this.pool.query(
268
+ `
269
+ SELECT TABLE_COMMENT
270
+ FROM INFORMATION_SCHEMA.TABLES
271
+ ${schemaClause}
272
+ AND TABLE_NAME = ?
273
+ `,
274
+ queryParams
275
+ );
276
+ if (rows.length > 0) {
277
+ return rows[0].TABLE_COMMENT || null;
278
+ }
279
+ return null;
280
+ } catch (error) {
281
+ return null;
282
+ }
283
+ }
284
+ async getStoredProcedures(schema, routineType) {
285
+ if (!this.pool) {
286
+ throw new Error("Not connected to database");
287
+ }
288
+ try {
289
+ const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
290
+ const queryParams = schema ? [schema] : [];
291
+ let typeFilter = "";
292
+ if (routineType === "function") {
293
+ typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
294
+ } else if (routineType === "procedure") {
295
+ typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
296
+ }
297
+ const rows = await this.pool.query(
298
+ `
299
+ SELECT ROUTINE_NAME
300
+ FROM INFORMATION_SCHEMA.ROUTINES
301
+ ${schemaClause}${typeFilter}
302
+ ORDER BY ROUTINE_NAME
303
+ `,
304
+ queryParams
305
+ );
306
+ return rows.map((row) => row.ROUTINE_NAME);
307
+ } catch (error) {
308
+ console.error("Error getting stored procedures:", error);
309
+ throw error;
310
+ }
311
+ }
312
+ async getStoredProcedureDetail(procedureName, schema) {
313
+ if (!this.pool) {
314
+ throw new Error("Not connected to database");
315
+ }
316
+ try {
317
+ const schemaClause = schema ? "WHERE r.ROUTINE_SCHEMA = ?" : "WHERE r.ROUTINE_SCHEMA = DATABASE()";
318
+ const queryParams = schema ? [schema, procedureName] : [procedureName];
319
+ const rows = await this.pool.query(
320
+ `
321
+ SELECT
322
+ r.ROUTINE_NAME AS procedure_name,
323
+ CASE
324
+ WHEN r.ROUTINE_TYPE = 'PROCEDURE' THEN 'procedure'
325
+ ELSE 'function'
326
+ END AS procedure_type,
327
+ LOWER(r.ROUTINE_TYPE) AS routine_type,
328
+ r.ROUTINE_DEFINITION,
329
+ r.DTD_IDENTIFIER AS return_type,
330
+ (
331
+ SELECT GROUP_CONCAT(
332
+ CONCAT(p.PARAMETER_NAME, ' ', p.PARAMETER_MODE, ' ', p.DATA_TYPE)
333
+ ORDER BY p.ORDINAL_POSITION
334
+ SEPARATOR ', '
335
+ )
336
+ FROM INFORMATION_SCHEMA.PARAMETERS p
337
+ WHERE p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA
338
+ AND p.SPECIFIC_NAME = r.ROUTINE_NAME
339
+ AND p.PARAMETER_NAME IS NOT NULL
340
+ ) AS parameter_list
341
+ FROM INFORMATION_SCHEMA.ROUTINES r
342
+ ${schemaClause}
343
+ AND r.ROUTINE_NAME = ?
344
+ `,
345
+ queryParams
346
+ );
347
+ if (rows.length === 0) {
348
+ const schemaName = schema || "current schema";
349
+ throw new Error(`Stored procedure '${procedureName}' not found in ${schemaName}`);
350
+ }
351
+ const procedure = rows[0];
352
+ let definition = procedure.ROUTINE_DEFINITION;
353
+ try {
354
+ const schemaValue = schema || await this.getCurrentSchema();
355
+ if (procedure.procedure_type === "procedure") {
356
+ try {
357
+ const defRows = await this.pool.query(`
358
+ SHOW CREATE PROCEDURE ${schemaValue}.${procedureName}
359
+ `);
360
+ if (defRows && defRows.length > 0) {
361
+ definition = defRows[0]["Create Procedure"];
362
+ }
363
+ } catch (err) {
364
+ console.error(`Error getting procedure definition with SHOW CREATE: ${err}`);
365
+ }
366
+ } else {
367
+ try {
368
+ const defRows = await this.pool.query(`
369
+ SHOW CREATE FUNCTION ${schemaValue}.${procedureName}
370
+ `);
371
+ if (defRows && defRows.length > 0) {
372
+ definition = defRows[0]["Create Function"];
373
+ }
374
+ } catch (innerErr) {
375
+ console.error(`Error getting function definition with SHOW CREATE: ${innerErr}`);
376
+ }
377
+ }
378
+ if (!definition) {
379
+ const bodyRows = await this.pool.query(
380
+ `
381
+ SELECT ROUTINE_DEFINITION, ROUTINE_BODY
382
+ FROM INFORMATION_SCHEMA.ROUTINES
383
+ WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ?
384
+ `,
385
+ [schemaValue, procedureName]
386
+ );
387
+ if (bodyRows && bodyRows.length > 0) {
388
+ if (bodyRows[0].ROUTINE_DEFINITION) {
389
+ definition = bodyRows[0].ROUTINE_DEFINITION;
390
+ } else if (bodyRows[0].ROUTINE_BODY) {
391
+ definition = bodyRows[0].ROUTINE_BODY;
392
+ }
393
+ }
394
+ }
395
+ } catch (error) {
396
+ console.error(`Error getting procedure/function details: ${error}`);
397
+ }
398
+ return {
399
+ procedure_name: procedure.procedure_name,
400
+ procedure_type: procedure.procedure_type,
401
+ language: "sql",
402
+ // MariaDB procedures are generally in SQL
403
+ parameter_list: procedure.parameter_list || "",
404
+ return_type: procedure.routine_type === "function" ? procedure.return_type : void 0,
405
+ definition: definition || void 0
406
+ };
407
+ } catch (error) {
408
+ console.error("Error getting stored procedure detail:", error);
409
+ throw error;
410
+ }
411
+ }
412
+ // Helper method to get current schema (database) name
413
+ async getCurrentSchema() {
414
+ const rows = await this.pool.query("SELECT DATABASE() AS DB");
415
+ return rows[0].DB;
416
+ }
417
+ async executeSQL(sql, options, parameters) {
418
+ if (!this.pool) {
419
+ throw new Error("Not connected to database");
420
+ }
421
+ const conn = await this.pool.getConnection();
422
+ try {
423
+ let processedSQL = sql;
424
+ if (options.maxRows) {
425
+ const statements = splitSQLStatements(sql, "mariadb");
426
+ const processedStatements = statements.map(
427
+ (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
428
+ );
429
+ processedSQL = processedStatements.join("; ");
430
+ if (sql.trim().endsWith(";")) {
431
+ processedSQL += ";";
432
+ }
433
+ }
434
+ let results;
435
+ if (parameters && parameters.length > 0) {
436
+ try {
437
+ results = await conn.query(processedSQL, parameters);
438
+ } catch (error) {
439
+ console.error(`[MariaDB executeSQL] ERROR: ${error.message}`);
440
+ console.error(`[MariaDB executeSQL] SQL: ${processedSQL}`);
441
+ console.error(`[MariaDB executeSQL] Parameters: ${JSON.stringify(parameters)}`);
442
+ throw error;
443
+ }
444
+ } else {
445
+ results = await conn.query(processedSQL);
446
+ }
447
+ const rows = parseQueryResults(results);
448
+ const rowCount = extractAffectedRows(results);
449
+ return { rows, rowCount };
450
+ } catch (error) {
451
+ console.error("Error executing query:", error);
452
+ throw error;
453
+ } finally {
454
+ conn.release();
455
+ }
456
+ }
457
+ };
458
+ var mariadbConnector = new MariaDBConnector();
459
+ ConnectorRegistry.register(mariadbConnector);
460
+ export {
461
+ MariaDBConnector
462
+ };