@bytebase/dbhub 1.2.0 → 1.2.2

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.
package/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  > [!NOTE]
2
2
  > Brought to you by [Bytebase](https://www.bytebase.com/), open-source database governance platform.
3
3
 
4
+ <p align="center">
5
+ <a href="https://www.star-history.com/bytebase/dbhub">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/badge?repo=bytebase/dbhub&type=trending&theme=dark" />
8
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/badge?repo=bytebase/dbhub&type=trending" />
9
+ <img alt="GitHub Trending Repository of the Day" src="https://api.star-history.com/badge?repo=bytebase/dbhub&type=trending" />
10
+ </picture>
11
+ </a>
12
+ </p>
13
+
4
14
  <p align="center">
5
15
  <a href="https://dbhub.ai/" target="_blank">
6
16
  <picture>
@@ -30,14 +40,33 @@
30
40
  MCP Clients MCP Server Databases
31
41
  ```
32
42
 
33
- DBHub is a token efficient, zero-dependency MCP server implementing the Model Context Protocol (MCP) server interface. This lightweight gateway allows MCP-compatible clients to connect to and explore different databases:
43
+ DBHub is a minimal MCP server: token-efficient, zero-dependency, and just two tools by default with opt-in extras. This lightweight gateway allows MCP-compatible clients to connect to and explore different databases:
34
44
 
35
- - **Local Development First**: Zero dependency, token efficient with a minimal set of MCP tools to maximize context window
45
+ - **Minimal**: Zero dependency, token efficient with a minimal set of MCP tools to maximize context window
36
46
  - **Multi-Database**: PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite through a single interface
37
47
  - **Multi-Connection**: Connect to multiple databases simultaneously with TOML configuration
38
48
  - **Guardrails**: Read-only mode, row limiting, and query timeout to prevent runaway operations
39
49
  - **Secure Access**: SSH tunneling and SSL/TLS encryption
40
50
 
51
+ > DBHub is the official example in the [Claude Code docs](https://code.claude.com/docs/en/mcp#example-query-your-postgresql-database) for connecting to PostgreSQL via MCP.
52
+
53
+ ## Token Efficiency
54
+
55
+ DBHub loads just 2 tools by default at **1.4k tokens** — 13-14x fewer than alternatives — keeping the context window open for your actual work.
56
+
57
+ | MCP Server | Default Config | Default Tools |
58
+ |------------|---------------|--------------|
59
+ | **DBHub** | **1.4k** | 2 (`execute_sql`, `search_objects`) |
60
+ | MCP Toolbox | 19.0k | 28 |
61
+ | Supabase MCP | 19.3k | all |
62
+
63
+ ## Use Cases
64
+
65
+ - **Local Development**: Schema exploration, query validation, and data debugging with Claude Code, VS Code, Cursor, etc.
66
+ - **Non-Technical Access**: Expose curated, read-only views to non-technical staff via Claude Desktop, VS Code, Cursor, etc.
67
+ - **Multi-Database Consolidation**: Replace separate MCP servers for each database with a single DBHub process
68
+ - **Production Troubleshooting**: Read-only diagnostics with guardrails against runaway queries
69
+
41
70
  ## Supported Databases
42
71
 
43
72
  PostgreSQL, MySQL, SQL Server, MariaDB, and SQLite.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  blankCommentsAndStrings,
3
3
  stripCommentsAndStrings
4
- } from "./chunk-WIQ4DWGY.js";
4
+ } from "./chunk-MF6LSVWK.js";
5
5
 
6
6
  // src/utils/sql-row-limiter.ts
7
7
  var SQLRowLimiter = class {
@@ -183,6 +183,61 @@ LIMIT ${maxRows}${hasSemicolon ? ";" : ""}`;
183
183
  }
184
184
  return this.applyTopToQuery(sql, maxRows);
185
185
  }
186
+ /**
187
+ * Like applyMaxRows, but requests one probe row (maxRows + 1) whenever the
188
+ * cap is the binding constraint, so callers can detect truncation exactly
189
+ * instead of guessing from `rowCount === maxRows` (which a table with
190
+ * exactly maxRows rows also produces).
191
+ *
192
+ * `probeApplied` is false when the cap cannot fire: no maxRows, not a
193
+ * SELECT, or the query's own literal LIMIT is already within the cap (the
194
+ * user asked for fewer rows than the cap — that is their limit, not
195
+ * truncation).
196
+ */
197
+ static applyMaxRowsWithTruncationProbe(sql, maxRows) {
198
+ if (!maxRows || !this.isSelectQuery(sql)) {
199
+ return { sql, probeApplied: false };
200
+ }
201
+ if (!this.hasParameterizedLimit(sql)) {
202
+ const existingLimit = this.extractLimitValue(sql);
203
+ if (existingLimit !== null && existingLimit <= maxRows) {
204
+ return { sql, probeApplied: false };
205
+ }
206
+ }
207
+ return { sql: this.applyMaxRows(sql, maxRows + 1), probeApplied: true };
208
+ }
209
+ /**
210
+ * SQL Server variant of applyMaxRowsWithTruncationProbe, using TOP syntax.
211
+ * A set-operator statement is always wrapped (any TOP on an individual
212
+ * branch caps only that branch, not the combined output), so the probe
213
+ * applies there regardless of branch-level TOPs.
214
+ */
215
+ static applyMaxRowsForSQLServerWithTruncationProbe(sql, maxRows) {
216
+ if (!maxRows || !this.isSelectQuery(sql)) {
217
+ return { sql, probeApplied: false };
218
+ }
219
+ if (!this.hasSetOperator(sql)) {
220
+ const existingTop = this.extractTopValue(sql);
221
+ if (existingTop !== null && existingTop <= maxRows) {
222
+ return { sql, probeApplied: false };
223
+ }
224
+ }
225
+ return { sql: this.applyMaxRowsForSQLServer(sql, maxRows + 1), probeApplied: true };
226
+ }
227
+ /**
228
+ * Post-process a result set produced by a truncation-probe rewrite: when
229
+ * the probe row came back (more than maxRows rows), drop it, clamp the
230
+ * count to maxRows, and mark the set truncated so consumers can tell a
231
+ * capped result from a complete one.
232
+ */
233
+ static flagTruncation(resultSet, maxRows, probeApplied) {
234
+ if (!probeApplied || !maxRows || resultSet.rows.length <= maxRows) {
235
+ return;
236
+ }
237
+ resultSet.rows = resultSet.rows.slice(0, maxRows);
238
+ resultSet.rowCount = maxRows;
239
+ resultSet.truncated = true;
240
+ }
186
241
  };
187
242
 
188
243
  // src/utils/resource-cleanup.ts
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-FU2ZJE4E.js";
5
5
  import {
6
6
  obfuscateDSNPassword
7
- } from "./chunk-WIQ4DWGY.js";
7
+ } from "./chunk-MF6LSVWK.js";
8
8
 
9
9
  // src/connectors/mysql-family-health-check.ts
10
10
  async function getMySQLFamilyHealthCheck(query) {
@@ -8,7 +8,7 @@ import {
8
8
  getDefaultPortForType,
9
9
  parseConnectionInfoFromDSN,
10
10
  stripCommentsAndStrings
11
- } from "./chunk-WIQ4DWGY.js";
11
+ } from "./chunk-MF6LSVWK.js";
12
12
 
13
13
  // src/tools/builtin-tools.ts
14
14
  var BUILTIN_TOOL_EXECUTE_SQL = "execute_sql";
@@ -1307,6 +1307,18 @@ function validateSourceConfig(source, configPath) {
1307
1307
  );
1308
1308
  }
1309
1309
  }
1310
+ if (source.aws_profile !== void 0) {
1311
+ if (typeof source.aws_profile !== "string" || source.aws_profile.trim().length === 0) {
1312
+ throw new Error(
1313
+ `Configuration file ${configPath}: source '${source.id}' has invalid aws_profile. Must be a non-empty string.`
1314
+ );
1315
+ }
1316
+ if (source.aws_iam_auth !== true) {
1317
+ throw new Error(
1318
+ `Configuration file ${configPath}: source '${source.id}' aws_profile requires aws_iam_auth = true.`
1319
+ );
1320
+ }
1321
+ }
1310
1322
  if (source.aws_iam_auth === true) {
1311
1323
  const validIamTypes = ["postgres", "mysql", "mariadb"];
1312
1324
  if (!source.type || !validIamTypes.includes(source.type)) {
@@ -1484,6 +1496,20 @@ function validateSourceConfig(source, configPath) {
1484
1496
  );
1485
1497
  }
1486
1498
  }
1499
+ function resolveSqliteDatabasePath(database, configPath) {
1500
+ if (database === ":memory:") {
1501
+ return database;
1502
+ }
1503
+ const expanded = expandHomeDir(database);
1504
+ const absolute = path2.isAbsolute(expanded) ? expanded : path2.resolve(path2.dirname(configPath), expanded);
1505
+ return absolute.replace(/\\/g, "/");
1506
+ }
1507
+ function buildSqliteDSN(database) {
1508
+ if (database === ":memory:") {
1509
+ return "sqlite:///:memory:";
1510
+ }
1511
+ return `sqlite://${database.startsWith("/") ? database : `/${database}`}`;
1512
+ }
1487
1513
  function processSourceConfigs(sources, configPath) {
1488
1514
  return sources.map((source) => {
1489
1515
  const processed = { ...source };
@@ -1494,7 +1520,7 @@ function processSourceConfigs(sources, configPath) {
1494
1520
  processed.sslrootcert = expandHomeDir(processed.sslrootcert);
1495
1521
  }
1496
1522
  if (processed.type === "sqlite" && processed.database) {
1497
- processed.database = expandHomeDir(processed.database);
1523
+ processed.database = resolveSqliteDatabasePath(processed.database, configPath);
1498
1524
  }
1499
1525
  if (processed.dsn && processed.dsn.startsWith("sqlite:///~")) {
1500
1526
  processed.dsn = `sqlite:///${expandHomeDir(processed.dsn.substring(11))}`;
@@ -1617,7 +1643,7 @@ function buildDSNFromSource(source) {
1617
1643
  `Source '${source.id}': 'database' field is required for SQLite`
1618
1644
  );
1619
1645
  }
1620
- return `sqlite:///${source.database}`;
1646
+ return buildSqliteDSN(source.database);
1621
1647
  }
1622
1648
  const isAwsIamPasswordless = source.aws_iam_auth === true && ["postgres", "mysql", "mariadb"].includes(source.type);
1623
1649
  const passwordRequired = source.authentication !== "azure-active-directory-access-token" && !isAwsIamPasswordless;
@@ -1677,12 +1703,17 @@ async function generateRdsAuthToken(params) {
1677
1703
  }
1678
1704
  throw error;
1679
1705
  }
1680
- const signer = new Signer({
1706
+ const signerConfig = {
1681
1707
  hostname: params.hostname,
1682
1708
  port: params.port,
1683
1709
  username: params.username,
1684
1710
  region: params.region
1685
- });
1711
+ };
1712
+ if (params.profile) {
1713
+ const { fromIni } = await import("@aws-sdk/credential-providers");
1714
+ signerConfig.credentials = fromIni({ profile: params.profile });
1715
+ }
1716
+ const signer = new Signer(signerConfig);
1686
1717
  return signer.getAuthToken();
1687
1718
  }
1688
1719
 
@@ -2158,7 +2189,8 @@ var ConnectorManager = class {
2158
2189
  hostname,
2159
2190
  port,
2160
2191
  username,
2161
- region: source.aws_region
2192
+ region: source.aws_region,
2193
+ profile: source.aws_profile
2162
2194
  });
2163
2195
  const queryParams = new Map(parsed.searchParams);
2164
2196
  const currentSslMode = queryParams.get("sslmode");
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  stripCommentsAndStrings
3
- } from "./chunk-WIQ4DWGY.js";
3
+ } from "./chunk-MF6LSVWK.js";
4
4
 
5
5
  // src/utils/allowed-keywords.ts
6
6
  var allowedKeywords = {
@@ -91,7 +91,9 @@ var SafeURL = class {
91
91
  }
92
92
  });
93
93
  }
94
- const atIndex = urlString.indexOf("@");
94
+ const pathStart = urlString.indexOf("/");
95
+ const authorityEnd = pathStart === -1 ? urlString.length : pathStart;
96
+ const atIndex = urlString.lastIndexOf("@", authorityEnd - 1);
95
97
  if (atIndex !== -1) {
96
98
  const auth = urlString.substring(0, atIndex);
97
99
  urlString = urlString.substring(atIndex + 1);
package/dist/index.js CHANGED
@@ -18,14 +18,14 @@ import {
18
18
  resolveSourceConfigs,
19
19
  resolveTomlConfigPath,
20
20
  resolveTransport
21
- } from "./chunk-CUBIR72I.js";
21
+ } from "./chunk-F5TPYAVO.js";
22
22
  import {
23
23
  allowedKeywords,
24
24
  getFirstKeyword,
25
25
  hasEscapeHatchFunction,
26
26
  isReadOnlySQL,
27
27
  sqlServerDynamicSqlPattern
28
- } from "./chunk-PBN2UXB5.js";
28
+ } from "./chunk-IQRBLAB4.js";
29
29
  import {
30
30
  loadConnectors
31
31
  } from "./chunk-WVVMH6FJ.js";
@@ -39,7 +39,7 @@ import {
39
39
  parseConnectionInfoFromDSN,
40
40
  splitSQLStatements,
41
41
  stripCommentsAndStrings
42
- } from "./chunk-WIQ4DWGY.js";
42
+ } from "./chunk-MF6LSVWK.js";
43
43
 
44
44
  // src/server.ts
45
45
  import { McpServer, createMcpHandler } from "@modelcontextprotocol/server";
@@ -224,7 +224,11 @@ function toStatementsPayload(resultSets) {
224
224
  return resultSets.map((set) => ({
225
225
  sql: set.sql,
226
226
  rows: set.rows,
227
- count: set.rowCount
227
+ count: set.rowCount,
228
+ // Only present when a max_rows cap actually cut off rows, so consumers
229
+ // can tell a capped result from a table with exactly max_rows rows
230
+ // (and complete results don't pay the extra tokens).
231
+ ...set.truncated ? { truncated: true } : {}
228
232
  }));
229
233
  }
230
234
  function resolveTrackedToolName(sourceId, baseName) {
@@ -1008,7 +1012,7 @@ function getExecuteSqlMetadata(sourceId) {
1008
1012
  maxRows: toolConfig?.max_rows
1009
1013
  };
1010
1014
  const readonlyNote = executeOptions.readonly ? " [READ-ONLY MODE]" : "";
1011
- const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows)` : "";
1015
+ const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows; capped results carry "truncated": true)` : "";
1012
1016
  const description = isSingleSource ? `${userDescPrefix}Execute SQL queries on the ${dbType} database${readonlyNote}${maxRowsNote}` : `${userDescPrefix}Execute SQL queries on the '${sourceId}' ${dbType} database${readonlyNote}${maxRowsNote}`;
1013
1017
  const isReadonly = isReadOnlyPolicy(policyFromReadonly(executeOptions.readonly));
1014
1018
  const annotations = {
@@ -1909,7 +1913,7 @@ See documentation for more details on configuring database connections.
1909
1913
  const sources = sourceConfigsData.sources;
1910
1914
  console.error(`Configuration source: ${sourceConfigsData.source}`);
1911
1915
  await connectorManager.connectWithSources(sources);
1912
- const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-XMEJF7WW.js");
1916
+ const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-6ABK3M6V.js");
1913
1917
  initializeToolRegistry2({
1914
1918
  sources: sourceConfigsData.sources,
1915
1919
  tools: sourceConfigsData.tools
@@ -2064,11 +2068,11 @@ See documentation for more details on configuring database connections.
2064
2068
 
2065
2069
  // src/index.ts
2066
2070
  var connectorModules = [
2067
- { load: () => import("./postgres-6XY2GPQV.js"), name: "PostgreSQL", driver: "pg" },
2068
- { load: () => import("./sqlserver-IIGRXGYK.js"), name: "SQL Server", driver: "mssql" },
2069
- { load: () => import("./sqlite-CZE6Z36Y.js"), name: "SQLite", driver: "node:sqlite" },
2070
- { load: () => import("./mysql-RLDQI3SJ.js"), name: "MySQL", driver: "mysql2" },
2071
- { load: () => import("./mariadb-E33EXGJZ.js"), name: "MariaDB", driver: "mariadb" }
2071
+ { load: () => import("./postgres-2FHS64EO.js"), name: "PostgreSQL", driver: "pg" },
2072
+ { load: () => import("./sqlserver-WNVSBFJ3.js"), name: "SQL Server", driver: "mssql" },
2073
+ { load: () => import("./sqlite-ASYMU5BI.js"), name: "SQLite", driver: "node:sqlite" },
2074
+ { load: () => import("./mysql-GXDUNLJ6.js"), name: "MySQL", driver: "mysql2" },
2075
+ { load: () => import("./mariadb-DQ6P57IX.js"), name: "MariaDB", driver: "mariadb" }
2072
2076
  ];
2073
2077
  loadConnectors(connectorModules).then(() => main()).catch((error) => {
2074
2078
  console.error("Fatal error:", error);
@@ -5,7 +5,7 @@ import {
5
5
  parseQueryResultSets,
6
6
  requireDatabaseInDSN,
7
7
  withReadOnlyTransaction
8
- } from "./chunk-QEFMUQJ2.js";
8
+ } from "./chunk-A4TSDCPY.js";
9
9
  import "./chunk-FU2ZJE4E.js";
10
10
  import {
11
11
  quoteIdentifier
@@ -13,13 +13,13 @@ import {
13
13
  import {
14
14
  SQLRowLimiter,
15
15
  closeQuietly
16
- } from "./chunk-RSB7DPO2.js";
16
+ } from "./chunk-6QETXS44.js";
17
17
  import {
18
18
  ConnectorRegistry,
19
19
  SafeURL,
20
20
  obfuscateDSNPassword,
21
21
  splitSQLStatements
22
- } from "./chunk-WIQ4DWGY.js";
22
+ } from "./chunk-MF6LSVWK.js";
23
23
 
24
24
  // src/connectors/mariadb/index.ts
25
25
  import * as mariadb from "mariadb";
@@ -506,11 +506,13 @@ var MariaDBConnector = class _MariaDBConnector {
506
506
  async () => {
507
507
  const statements = splitSQLStatements(sql, "mariadb");
508
508
  let processedSQL = sql;
509
+ let probes = [];
509
510
  if (options.maxRows) {
510
- const processedStatements = statements.map(
511
- (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
511
+ const rewrites = statements.map(
512
+ (statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows)
512
513
  );
513
- processedSQL = processedStatements.join("; ");
514
+ probes = rewrites.map((rewrite) => rewrite.probeApplied);
515
+ processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
514
516
  if (sql.trim().endsWith(";")) {
515
517
  processedSQL += ";";
516
518
  }
@@ -522,6 +524,11 @@ var MariaDBConnector = class _MariaDBConnector {
522
524
  results = await conn.query(processedSQL);
523
525
  }
524
526
  const resultSets = parseQueryResultSets(results, statements);
527
+ if (resultSets.length === probes.length) {
528
+ resultSets.forEach(
529
+ (set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
530
+ );
531
+ }
525
532
  return { resultSets };
526
533
  }
527
534
  );
@@ -6,7 +6,7 @@ import {
6
6
  parseQueryResultSets,
7
7
  requireDatabaseInDSN,
8
8
  withReadOnlyTransaction
9
- } from "./chunk-QEFMUQJ2.js";
9
+ } from "./chunk-A4TSDCPY.js";
10
10
  import "./chunk-FU2ZJE4E.js";
11
11
  import {
12
12
  quoteIdentifier
@@ -14,13 +14,13 @@ import {
14
14
  import {
15
15
  SQLRowLimiter,
16
16
  closeQuietly
17
- } from "./chunk-RSB7DPO2.js";
17
+ } from "./chunk-6QETXS44.js";
18
18
  import {
19
19
  ConnectorRegistry,
20
20
  SafeURL,
21
21
  obfuscateDSNPassword,
22
22
  splitSQLStatements
23
- } from "./chunk-WIQ4DWGY.js";
23
+ } from "./chunk-MF6LSVWK.js";
24
24
 
25
25
  // src/connectors/mysql/index.ts
26
26
  import mysql from "mysql2/promise";
@@ -517,11 +517,13 @@ var _MySQLConnector = class _MySQLConnector {
517
517
  async () => {
518
518
  const statements = splitSQLStatements(sql, "mysql");
519
519
  let processedSQL = sql;
520
+ let probes = [];
520
521
  if (options.maxRows) {
521
- const processedStatements = statements.map(
522
- (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
522
+ const rewrites = statements.map(
523
+ (statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows)
523
524
  );
524
- processedSQL = processedStatements.join("; ");
525
+ probes = rewrites.map((rewrite) => rewrite.probeApplied);
526
+ processedSQL = rewrites.map((rewrite) => rewrite.sql).join("; ");
525
527
  if (sql.trim().endsWith(";")) {
526
528
  processedSQL += ";";
527
529
  }
@@ -534,6 +536,11 @@ var _MySQLConnector = class _MySQLConnector {
534
536
  }
535
537
  const [firstResult] = results;
536
538
  const resultSets = parseQueryResultSets(firstResult, statements);
539
+ if (resultSets.length === probes.length) {
540
+ resultSets.forEach(
541
+ (set, index) => SQLRowLimiter.flagTruncation(set, options.maxRows, probes[index])
542
+ );
543
+ }
537
544
  return { resultSets };
538
545
  }
539
546
  );
@@ -7,13 +7,13 @@ import {
7
7
  import {
8
8
  SQLRowLimiter,
9
9
  closeQuietly
10
- } from "./chunk-RSB7DPO2.js";
10
+ } from "./chunk-6QETXS44.js";
11
11
  import {
12
12
  ConnectorRegistry,
13
13
  SafeURL,
14
14
  obfuscateDSNPassword,
15
15
  splitSQLStatements
16
- } from "./chunk-WIQ4DWGY.js";
16
+ } from "./chunk-MF6LSVWK.js";
17
17
 
18
18
  // src/connectors/postgres/index.ts
19
19
  import fs from "fs";
@@ -557,17 +557,22 @@ var PostgresConnector = class _PostgresConnector {
557
557
  try {
558
558
  const statements = splitSQLStatements(sql, "postgres");
559
559
  if (statements.length === 1) {
560
- const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
560
+ const { sql: processedStatement, probeApplied } = SQLRowLimiter.applyMaxRowsWithTruncationProbe(
561
+ statements[0],
562
+ options.maxRows
563
+ );
561
564
  if (options.readonly) {
562
565
  await client.query("BEGIN READ ONLY");
563
566
  try {
564
567
  const result2 = parameters && parameters.length > 0 ? await client.query(processedStatement, parameters) : await client.query(processedStatement);
565
568
  await client.query("COMMIT");
566
- return {
567
- resultSets: [
568
- { sql: statements[0], rows: result2.rows, rowCount: result2.rowCount ?? result2.rows.length }
569
- ]
569
+ const resultSet2 = {
570
+ sql: statements[0],
571
+ rows: result2.rows,
572
+ rowCount: result2.rowCount ?? result2.rows.length
570
573
  };
574
+ SQLRowLimiter.flagTruncation(resultSet2, options.maxRows, probeApplied);
575
+ return { resultSets: [resultSet2] };
571
576
  } catch (error) {
572
577
  try {
573
578
  await client.query("ROLLBACK");
@@ -582,11 +587,13 @@ var PostgresConnector = class _PostgresConnector {
582
587
  } else {
583
588
  result = await client.query(processedStatement);
584
589
  }
585
- return {
586
- resultSets: [
587
- { sql: statements[0], rows: result.rows, rowCount: result.rowCount ?? result.rows.length }
588
- ]
590
+ const resultSet = {
591
+ sql: statements[0],
592
+ rows: result.rows,
593
+ rowCount: result.rowCount ?? result.rows.length
589
594
  };
595
+ SQLRowLimiter.flagTruncation(resultSet, options.maxRows, probeApplied);
596
+ return { resultSets: [resultSet] };
590
597
  } else {
591
598
  if (parameters && parameters.length > 0) {
592
599
  throw new Error("Parameters are not supported for multi-statement queries in PostgreSQL");
@@ -595,13 +602,18 @@ var PostgresConnector = class _PostgresConnector {
595
602
  await client.query(options.readonly ? "BEGIN READ ONLY" : "BEGIN");
596
603
  try {
597
604
  for (let statement of statements) {
598
- const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
605
+ const { sql: processedStatement, probeApplied } = SQLRowLimiter.applyMaxRowsWithTruncationProbe(
606
+ statement,
607
+ options.maxRows
608
+ );
599
609
  const result = await client.query(processedStatement);
600
- resultSets.push({
610
+ const resultSet = {
601
611
  sql: statement,
602
612
  rows: result.rows ?? [],
603
613
  rowCount: result.rowCount ?? result.rows?.length ?? 0
604
- });
614
+ };
615
+ SQLRowLimiter.flagTruncation(resultSet, options.maxRows, probeApplied);
616
+ resultSets.push(resultSet);
605
617
  }
606
618
  await client.query("COMMIT");
607
619
  } catch (error) {