@bytebase/dbhub 1.1.0 → 1.2.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.
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>DBHub - Minimal Database MCP Server</title>
8
- <script type="module" crossorigin src="/assets/index-DBYlgGks.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DOZ5HOZ9.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-BJ-1UrcV.css">
10
10
  </head>
11
11
  <body>
@@ -2,9 +2,9 @@ import {
2
2
  ToolRegistry,
3
3
  getToolRegistry,
4
4
  initializeToolRegistry
5
- } from "./chunk-GSSLHJB6.js";
5
+ } from "./chunk-CUBIR72I.js";
6
6
  import "./chunk-WVVMH6FJ.js";
7
- import "./chunk-JEZZN2YZ.js";
7
+ import "./chunk-WIQ4DWGY.js";
8
8
  export {
9
9
  ToolRegistry,
10
10
  getToolRegistry,
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  SQLRowLimiter,
6
6
  closeQuietly
7
- } from "./chunk-S2V6RZ3C.js";
7
+ } from "./chunk-RSB7DPO2.js";
8
8
  import {
9
9
  ConnectorRegistry,
10
10
  SafeURL,
11
11
  obfuscateDSNPassword,
12
12
  splitSQLStatements
13
- } from "./chunk-JEZZN2YZ.js";
13
+ } from "./chunk-WIQ4DWGY.js";
14
14
 
15
15
  // src/connectors/sqlite/suppress-experimental-warning.ts
16
16
  var installed = false;
@@ -340,10 +340,10 @@ var SQLiteConnector = class _SQLiteConnector {
340
340
  if (isReadStatement) {
341
341
  if (parameters && parameters.length > 0) {
342
342
  const rows = this.prepare(processedStatement).all(...parameters);
343
- return { rows, rowCount: rows.length };
343
+ return { resultSets: [{ sql: statements[0], rows, rowCount: rows.length }] };
344
344
  } else {
345
345
  const rows = this.prepare(processedStatement).all();
346
- return { rows, rowCount: rows.length };
346
+ return { resultSets: [{ sql: statements[0], rows, rowCount: rows.length }] };
347
347
  }
348
348
  } else {
349
349
  let result;
@@ -352,7 +352,9 @@ var SQLiteConnector = class _SQLiteConnector {
352
352
  } else {
353
353
  result = this.prepare(processedStatement).run();
354
354
  }
355
- return { rows: [], rowCount: Number(result.changes) };
355
+ return {
356
+ resultSets: [{ sql: statements[0], rows: [], rowCount: Number(result.changes) }]
357
+ };
356
358
  }
357
359
  } else {
358
360
  if (parameters && parameters.length > 0) {
@@ -368,24 +370,23 @@ var SQLiteConnector = class _SQLiteConnector {
368
370
  writeStatements.push(statement);
369
371
  }
370
372
  }
371
- let totalChanges = 0;
373
+ const resultSets = [];
372
374
  for (const statement of writeStatements) {
373
375
  if (options.readonly) {
374
376
  this.db.exec("PRAGMA query_only = ON");
375
377
  }
376
378
  const result = this.prepare(statement).run();
377
- totalChanges += Number(result.changes);
379
+ resultSets.push({ sql: statement, rows: [], rowCount: Number(result.changes) });
378
380
  }
379
- let allRows = [];
380
- for (let statement of readStatements) {
381
+ for (const statement of readStatements) {
381
382
  if (options.readonly) {
382
383
  this.db.exec("PRAGMA query_only = ON");
383
384
  }
384
- statement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
385
- const result = this.prepare(statement).all();
386
- allRows.push(...result);
385
+ const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
386
+ const rows = this.prepare(processedStatement).all();
387
+ resultSets.push({ sql: statement, rows, rowCount: rows.length });
387
388
  }
388
- return { rows: allRows, rowCount: totalChanges + allRows.length };
389
+ return { resultSets };
389
390
  }
390
391
  } finally {
391
392
  if (options.readonly) {
@@ -3,7 +3,7 @@ import {
3
3
  sqlServerDynamicSqlPattern,
4
4
  sqlServerPassThroughKeywords,
5
5
  sqlServerPassThroughPattern
6
- } from "./chunk-EPOKWSFC.js";
6
+ } from "./chunk-PBN2UXB5.js";
7
7
  import {
8
8
  isDriverNotInstalled
9
9
  } from "./chunk-WVVMH6FJ.js";
@@ -14,13 +14,14 @@ import {
14
14
  import {
15
15
  SQLRowLimiter,
16
16
  closeQuietly
17
- } from "./chunk-S2V6RZ3C.js";
17
+ } from "./chunk-RSB7DPO2.js";
18
18
  import {
19
19
  ConnectorRegistry,
20
20
  SafeURL,
21
21
  obfuscateDSNPassword,
22
+ splitSQLStatements,
22
23
  stripCommentsAndStrings
23
- } from "./chunk-JEZZN2YZ.js";
24
+ } from "./chunk-WIQ4DWGY.js";
24
25
 
25
26
  // src/connectors/sqlserver/index.ts
26
27
  import sql from "mssql";
@@ -564,8 +565,9 @@ var _SQLServerConnector = class _SQLServerConnector {
564
565
  if (options.maxRows) {
565
566
  processedSQL = SQLRowLimiter.applyMaxRowsForSQLServer(sqlQuery, options.maxRows);
566
567
  }
568
+ const isSingleStatement = splitSQLStatements(processedSQL, "sqlserver").length === 1;
567
569
  if (options.readonly) {
568
- return await this.executeReadOnly(processedSQL, parameters);
570
+ return await this.executeReadOnly(processedSQL, parameters, isSingleStatement);
569
571
  }
570
572
  const request = this.connection.request();
571
573
  const messages = [];
@@ -584,14 +586,57 @@ var _SQLServerConnector = class _SQLServerConnector {
584
586
  _SQLServerConnector.bindParameters(request, parameters);
585
587
  const result = await request.query(processedSQL);
586
588
  return {
587
- rows: result.recordset || [],
588
- rowCount: result.rowsAffected[0] || 0,
589
+ resultSets: _SQLServerConnector.buildResultSets(
590
+ result.recordsets,
591
+ result.rowsAffected,
592
+ isSingleStatement ? processedSQL : void 0
593
+ ),
589
594
  ...messages.length > 0 ? { messages } : {}
590
595
  };
591
596
  } catch (error) {
592
597
  throw new Error(`Failed to execute query: ${error.message}`);
593
598
  }
594
599
  }
600
+ /**
601
+ * Builds one result set per SELECT-producing statement in the batch, plus
602
+ * (if any) a trailing result set summarizing the write-only statements.
603
+ *
604
+ * node-mssql only pushes a `recordsets` entry for statements that produce
605
+ * columns (SELECT); INSERT/UPDATE/DELETE statements in the same batch
606
+ * contribute to `rowsAffected` only, with no way to recover which
607
+ * `rowsAffected` entry belongs to which statement from the driver's final
608
+ * arrays (`recordsets`/`rowsAffected` aren't index-aligned - see the
609
+ * tedious `doneHandler`, which always pushes to `rowsAffected` but only
610
+ * conditionally to `recordsets`). So a batch of pure writes collapses to a
611
+ * single summed result set, and a batch mixing writes with selects gets
612
+ * one result set per select plus one trailing "writes" set for the rest -
613
+ * not a truly per-statement breakdown, but no read statement's rows are
614
+ * ever merged with another's, which is what mattered for #380.
615
+ *
616
+ * `sourceSql`, when given, is attributed to the single resulting set - it
617
+ * must be omitted (pass `undefined`) unless the caller has already
618
+ * confirmed the batch is unambiguously one statement, since for a genuine
619
+ * multi-statement batch there's no reliable way to say which source
620
+ * statement a given recordset (or the trailing writes set) came from.
621
+ */
622
+ static buildResultSets(recordsets, rowsAffected, sourceSql) {
623
+ const sets = (recordsets ?? []).map((recordset) => {
624
+ const rows = recordset ?? [];
625
+ return { rows, rowCount: rows.length };
626
+ });
627
+ const totalAffected = (rowsAffected ?? []).reduce((total, count) => total + (count ?? 0), 0);
628
+ const accountedFor = sets.reduce((total, set) => total + set.rowCount, 0);
629
+ const writesOnly = totalAffected - accountedFor;
630
+ if (sets.length === 0) {
631
+ sets.push({ rows: [], rowCount: totalAffected });
632
+ } else if (writesOnly > 0) {
633
+ sets.push({ rows: [], rowCount: writesOnly });
634
+ }
635
+ if (sets.length === 1 && sourceSql !== void 0) {
636
+ sets[0].sql = sourceSql;
637
+ }
638
+ return sets;
639
+ }
595
640
  /**
596
641
  * Bind positional parameters as @p1, @p2, ... inferring the SQL Server type
597
642
  * from each JavaScript value. Shared by every execution path so they cannot
@@ -673,7 +718,7 @@ var _SQLServerConnector = class _SQLServerConnector {
673
718
  * Dangerous constructs are rejected before the transaction opens; see
674
719
  * assertNoReadOnlyEscapes.
675
720
  */
676
- async executeReadOnly(processedSQL, parameters) {
721
+ async executeReadOnly(processedSQL, parameters, isSingleStatement) {
677
722
  this.assertNoReadOnlyEscapes(processedSQL, { transactionControl: true });
678
723
  const transaction = new sql.Transaction(this.connection);
679
724
  await transaction.begin();
@@ -710,8 +755,11 @@ var _SQLServerConnector = class _SQLServerConnector {
710
755
  }
711
756
  }
712
757
  return {
713
- rows: result.recordset || [],
714
- rowCount: result.rowsAffected[0] || 0,
758
+ resultSets: _SQLServerConnector.buildResultSets(
759
+ result.recordsets,
760
+ result.rowsAffected,
761
+ isSingleStatement ? processedSQL : void 0
762
+ ),
715
763
  ...messages.length > 0 ? { messages } : {}
716
764
  };
717
765
  }
@@ -756,8 +804,12 @@ var _SQLServerConnector = class _SQLServerConnector {
756
804
  const planRow = planResult.recordset?.[0];
757
805
  const planXml = planRow ? Object.values(planRow)[0] : null;
758
806
  return {
759
- rows: planXml != null ? [{ plan: planXml }] : [],
760
- rowCount: planXml != null ? 1 : 0
807
+ resultSets: [
808
+ {
809
+ rows: planXml != null ? [{ plan: planXml }] : [],
810
+ rowCount: planXml != null ? 1 : 0
811
+ }
812
+ ]
761
813
  };
762
814
  } catch (error) {
763
815
  throw new Error(`Failed to explain query: ${error.message}`);
@@ -878,8 +930,12 @@ var _SQLServerConnector = class _SQLServerConnector {
878
930
  }
879
931
  const planXml = _SQLServerConnector.extractPlanXml(planResult);
880
932
  return {
881
- rows: planXml != null ? [{ plan: planXml }] : [],
882
- rowCount: planXml != null ? 1 : 0
933
+ resultSets: [
934
+ {
935
+ rows: planXml != null ? [{ plan: planXml }] : [],
936
+ rowCount: planXml != null ? 1 : 0
937
+ }
938
+ ]
883
939
  };
884
940
  } catch (error) {
885
941
  throw new Error(`Failed to explain analyze query: ${error.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebase/dbhub",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "mcpName": "io.github.bytebase/dbhub",
5
5
  "description": "Minimal, token-efficient Database MCP Server for PostgreSQL, MySQL, SQL Server, SQLite, MariaDB",
6
6
  "repository": {
@@ -33,7 +33,9 @@
33
33
  "test:unit": "vitest run --project unit",
34
34
  "test:watch": "vitest",
35
35
  "test:integration": "vitest run --project integration",
36
- "test:build": "node scripts/smoke-test-build.mjs"
36
+ "test:build": "node scripts/smoke-test-build.mjs",
37
+ "sync-version": "node scripts/sync-version.mjs",
38
+ "version": "pnpm run sync-version && git add plugin/.claude-plugin/plugin.json plugin/.mcp.json server.json"
37
39
  },
38
40
  "engines": {
39
41
  "node": ">=22.5.0"