@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 +31 -2
- package/dist/{chunk-RSB7DPO2.js → chunk-6QETXS44.js} +56 -1
- package/dist/{chunk-QEFMUQJ2.js → chunk-A4TSDCPY.js} +1 -1
- package/dist/{chunk-CUBIR72I.js → chunk-F5TPYAVO.js} +38 -6
- package/dist/{chunk-PBN2UXB5.js → chunk-IQRBLAB4.js} +1 -1
- package/dist/{chunk-WIQ4DWGY.js → chunk-MF6LSVWK.js} +3 -1
- package/dist/index.js +15 -11
- package/dist/{mariadb-E33EXGJZ.js → mariadb-DQ6P57IX.js} +13 -6
- package/dist/{mysql-RLDQI3SJ.js → mysql-GXDUNLJ6.js} +13 -6
- package/dist/{postgres-6XY2GPQV.js → postgres-2FHS64EO.js} +26 -14
- package/dist/public/assets/{index-DOZ5HOZ9.js → index-BdBaFSHd.js} +38 -38
- package/dist/public/index.html +1 -1
- package/dist/{registry-XMEJF7WW.js → registry-6ABK3M6V.js} +2 -2
- package/dist/{sqlite-CZE6Z36Y.js → sqlite-ASYMU5BI.js} +20 -12
- package/dist/{sqlserver-IIGRXGYK.js → sqlserver-WNVSBFJ3.js} +36 -16
- package/package.json +2 -1
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
|
|
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
|
-
- **
|
|
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-
|
|
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
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getDefaultPortForType,
|
|
9
9
|
parseConnectionInfoFromDSN,
|
|
10
10
|
stripCommentsAndStrings
|
|
11
|
-
} from "./chunk-
|
|
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 =
|
|
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
|
|
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
|
|
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");
|
|
@@ -91,7 +91,9 @@ var SafeURL = class {
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
const
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
2068
|
-
{ load: () => import("./sqlserver-
|
|
2069
|
-
{ load: () => import("./sqlite-
|
|
2070
|
-
{ load: () => import("./mysql-
|
|
2071
|
-
{ load: () => import("./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-
|
|
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-
|
|
16
|
+
} from "./chunk-6QETXS44.js";
|
|
17
17
|
import {
|
|
18
18
|
ConnectorRegistry,
|
|
19
19
|
SafeURL,
|
|
20
20
|
obfuscateDSNPassword,
|
|
21
21
|
splitSQLStatements
|
|
22
|
-
} from "./chunk-
|
|
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
|
|
511
|
-
(statement) => SQLRowLimiter.
|
|
511
|
+
const rewrites = statements.map(
|
|
512
|
+
(statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows)
|
|
512
513
|
);
|
|
513
|
-
|
|
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-
|
|
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-
|
|
17
|
+
} from "./chunk-6QETXS44.js";
|
|
18
18
|
import {
|
|
19
19
|
ConnectorRegistry,
|
|
20
20
|
SafeURL,
|
|
21
21
|
obfuscateDSNPassword,
|
|
22
22
|
splitSQLStatements
|
|
23
|
-
} from "./chunk-
|
|
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
|
|
522
|
-
(statement) => SQLRowLimiter.
|
|
522
|
+
const rewrites = statements.map(
|
|
523
|
+
(statement) => SQLRowLimiter.applyMaxRowsWithTruncationProbe(statement, options.maxRows)
|
|
523
524
|
);
|
|
524
|
-
|
|
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-
|
|
10
|
+
} from "./chunk-6QETXS44.js";
|
|
11
11
|
import {
|
|
12
12
|
ConnectorRegistry,
|
|
13
13
|
SafeURL,
|
|
14
14
|
obfuscateDSNPassword,
|
|
15
15
|
splitSQLStatements
|
|
16
|
-
} from "./chunk-
|
|
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.
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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.
|
|
605
|
+
const { sql: processedStatement, probeApplied } = SQLRowLimiter.applyMaxRowsWithTruncationProbe(
|
|
606
|
+
statement,
|
|
607
|
+
options.maxRows
|
|
608
|
+
);
|
|
599
609
|
const result = await client.query(processedStatement);
|
|
600
|
-
|
|
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) {
|