@bytebase/dbhub 0.22.4 → 0.22.6
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 +2 -0
- package/dist/{chunk-BRXZ5ZQB.js → chunk-BXXHWEQS.js} +1 -1
- package/dist/{chunk-C7WEAPX4.js → chunk-JEZZN2YZ.js} +15 -1
- package/dist/{chunk-DN45LGY4.js → chunk-WPXPKRDU.js} +17 -4
- package/dist/index.js +122 -22
- package/dist/{mariadb-ERB4D3RP.js → mariadb-JPEKVFEW.js} +14 -2
- package/dist/{mysql-WG6UFALX.js → mysql-EK6TCER5.js} +14 -2
- package/dist/{postgres-EKVOBAH6.js → postgres-BKBCGDZS.js} +26 -4
- package/dist/{registry-OF5GPCBK.js → registry-IPRZ55YC.js} +2 -2
- package/dist/{sqlite-2GBIF3MQ.js → sqlite-CQ4LCBB6.js} +18 -4
- package/dist/{sqlserver-5TFVBCMA.js → sqlserver-47APXMSF.js} +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,8 @@ npx @bytebase/dbhub@latest --transport http --host 127.0.0.1 --port 8080 --demo
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
> The HTTP transport defaults to `--host 0.0.0.0`, exposing DBHub on every network interface. For production, bind to `127.0.0.1` and front DBHub with a reverse proxy (nginx/Caddy) or firewall — DBHub does not authenticate HTTP clients.
|
|
96
|
+
>
|
|
97
|
+
> The HTTP transport also has built-in DNS-rebinding protection: it only accepts requests whose `Host` is loopback, this machine's own hostname/IPs, or a name you allow via [`--allowed-hosts`](https://dbhub.ai/config/command-line#allowed-hosts). If a client behind a reverse proxy or custom DNS name gets a `403`, add that hostname with `--allowed-hosts`.
|
|
96
98
|
|
|
97
99
|
See [Command-Line Options](https://dbhub.ai/config/command-line) for all available parameters.
|
|
98
100
|
|
|
@@ -267,6 +267,20 @@ function scanSingleLineComment(sql, i) {
|
|
|
267
267
|
}
|
|
268
268
|
return { type: TokenType.Comment, end: j };
|
|
269
269
|
}
|
|
270
|
+
function scanSingleLineCommentMySQL(sql, i) {
|
|
271
|
+
if (sql[i] !== "-" || sql[i + 1] !== "-") {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const next = sql[i + 2];
|
|
275
|
+
if (next !== void 0 && next.charCodeAt(0) > 32 && next.charCodeAt(0) !== 127) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
let j = i;
|
|
279
|
+
while (j < sql.length && sql[j] !== "\n") {
|
|
280
|
+
j++;
|
|
281
|
+
}
|
|
282
|
+
return { type: TokenType.Comment, end: j };
|
|
283
|
+
}
|
|
270
284
|
function scanMultiLineComment(sql, i) {
|
|
271
285
|
if (sql[i] !== "/" || sql[i + 1] !== "*") {
|
|
272
286
|
return null;
|
|
@@ -405,7 +419,7 @@ function scanTokenPostgres(sql, i) {
|
|
|
405
419
|
return scanSingleLineComment(sql, i) ?? scanNestedMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanDollarQuotedBlock(sql, i) ?? plainToken(i);
|
|
406
420
|
}
|
|
407
421
|
function scanTokenMySQL(sql, i) {
|
|
408
|
-
return
|
|
422
|
+
return scanSingleLineCommentMySQL(sql, i) ?? scanMultiLineCommentMySQL(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBacktickQuotedIdentifier(sql, i) ?? plainToken(i);
|
|
409
423
|
}
|
|
410
424
|
function scanTokenSQLite(sql, i) {
|
|
411
425
|
return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBacktickQuotedIdentifier(sql, i) ?? scanBracketQuotedIdentifier(sql, i) ?? plainToken(i);
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getDefaultPortForType,
|
|
9
9
|
parseConnectionInfoFromDSN,
|
|
10
10
|
stripCommentsAndStrings
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-JEZZN2YZ.js";
|
|
12
12
|
|
|
13
13
|
// src/tools/builtin-tools.ts
|
|
14
14
|
var BUILTIN_TOOL_EXECUTE_SQL = "execute_sql";
|
|
@@ -733,6 +733,21 @@ function resolveHost() {
|
|
|
733
733
|
}
|
|
734
734
|
return { host: "0.0.0.0", source: "default" };
|
|
735
735
|
}
|
|
736
|
+
function resolveAllowedHosts() {
|
|
737
|
+
const args = parseCommandLineArgs();
|
|
738
|
+
const cliValue = requireFlagValue("allowed-hosts", args, "db.internal,app.example.com");
|
|
739
|
+
if (cliValue !== void 0) {
|
|
740
|
+
return { hosts: splitHostList(cliValue), source: "command line argument" };
|
|
741
|
+
}
|
|
742
|
+
const envValue = process.env.DBHUB_ALLOWED_HOSTS?.trim();
|
|
743
|
+
if (envValue) {
|
|
744
|
+
return { hosts: splitHostList(envValue), source: "environment variable" };
|
|
745
|
+
}
|
|
746
|
+
return { hosts: [], source: "default" };
|
|
747
|
+
}
|
|
748
|
+
function splitHostList(value) {
|
|
749
|
+
return value.split(",").map((h) => h.trim()).filter((h) => h.length > 0);
|
|
750
|
+
}
|
|
736
751
|
function redactDSN(dsn) {
|
|
737
752
|
try {
|
|
738
753
|
const url = new URL(dsn);
|
|
@@ -1787,9 +1802,6 @@ var ConnectorManager = class {
|
|
|
1787
1802
|
if (source.query_timeout !== void 0 && connector.id !== "sqlite") {
|
|
1788
1803
|
config.queryTimeoutSeconds = source.query_timeout;
|
|
1789
1804
|
}
|
|
1790
|
-
if (source.readonly !== void 0) {
|
|
1791
|
-
config.readonly = source.readonly;
|
|
1792
|
-
}
|
|
1793
1805
|
if (source.search_path) {
|
|
1794
1806
|
config.searchPath = source.search_path;
|
|
1795
1807
|
}
|
|
@@ -2371,6 +2383,7 @@ export {
|
|
|
2371
2383
|
resolveTransport,
|
|
2372
2384
|
resolvePort,
|
|
2373
2385
|
resolveHost,
|
|
2386
|
+
resolveAllowedHosts,
|
|
2374
2387
|
resolveSourceConfigs,
|
|
2375
2388
|
BUILTIN_TOOL_EXECUTE_SQL,
|
|
2376
2389
|
BUILTIN_TOOL_SEARCH_OBJECTS,
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
isDemoMode,
|
|
10
10
|
loadTomlConfig,
|
|
11
11
|
mapArgumentsToArray,
|
|
12
|
+
resolveAllowedHosts,
|
|
12
13
|
resolveHost,
|
|
13
14
|
resolvePort,
|
|
14
15
|
resolveSourceConfigs,
|
|
15
16
|
resolveTomlConfigPath,
|
|
16
17
|
resolveTransport
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-WPXPKRDU.js";
|
|
18
19
|
import {
|
|
19
20
|
loadConnectors
|
|
20
21
|
} from "./chunk-WVVMH6FJ.js";
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
parseConnectionInfoFromDSN,
|
|
29
30
|
splitSQLStatements,
|
|
30
31
|
stripCommentsAndStrings
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-JEZZN2YZ.js";
|
|
32
33
|
|
|
33
34
|
// src/server.ts
|
|
34
35
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -133,6 +134,13 @@ var mutatingPatterns = {
|
|
|
133
134
|
sqlserver: mutatingPattern
|
|
134
135
|
};
|
|
135
136
|
var selectIntoPattern = /\bselect\b[\s\S]+\binto\b/i;
|
|
137
|
+
var sqliteReadOnlyArgPragmas = /* @__PURE__ */ new Set([
|
|
138
|
+
"table_info",
|
|
139
|
+
"index_info",
|
|
140
|
+
"index_list",
|
|
141
|
+
"foreign_key_list"
|
|
142
|
+
]);
|
|
143
|
+
var sqlitePragmaParenPattern = /^pragma\s+(?:[a-z0-9_]+\.)?([a-z0-9_]+)\s*\(/;
|
|
136
144
|
var explainAnalyzePattern = /^explain\s+(?:\([^)]*\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)[^)]*\)|\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)(?:\s+verbose\b)?)/i;
|
|
137
145
|
function isReadOnlySQL(sql, connectorType) {
|
|
138
146
|
return checkReadOnly(
|
|
@@ -155,6 +163,15 @@ function checkReadOnly(cleanedSQL, connectorType) {
|
|
|
155
163
|
return false;
|
|
156
164
|
}
|
|
157
165
|
}
|
|
166
|
+
if (firstWord === "pragma" && connectorType === "sqlite") {
|
|
167
|
+
if (cleanedSQL.includes("=")) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const parenMatch = cleanedSQL.match(sqlitePragmaParenPattern);
|
|
171
|
+
if (parenMatch && !sqliteReadOnlyArgPragmas.has(parenMatch[1])) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
158
175
|
if ((firstWord === "select" || firstWord === "with") && selectIntoPattern.test(cleanedSQL)) {
|
|
159
176
|
return false;
|
|
160
177
|
}
|
|
@@ -1490,22 +1507,78 @@ Detected change in ${configPath}, reloading configuration...`);
|
|
|
1490
1507
|
}
|
|
1491
1508
|
|
|
1492
1509
|
// src/utils/cross-origin.ts
|
|
1510
|
+
import os from "node:os";
|
|
1493
1511
|
var INVALID_HOST_CHARS = /[\s/\\@?#]/;
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1512
|
+
var LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
|
|
1513
|
+
var ALLOW_ANY_HOST = "*";
|
|
1514
|
+
function normalizeHost(raw) {
|
|
1515
|
+
const trimmed = raw.trim();
|
|
1516
|
+
if (!trimmed) return null;
|
|
1517
|
+
if (trimmed === ALLOW_ANY_HOST) return ALLOW_ANY_HOST;
|
|
1518
|
+
if (INVALID_HOST_CHARS.test(trimmed)) return null;
|
|
1519
|
+
try {
|
|
1520
|
+
const hostname = new URL(`http://${trimmed}`).hostname.toLowerCase();
|
|
1521
|
+
return hostname || null;
|
|
1522
|
+
} catch {
|
|
1523
|
+
return null;
|
|
1499
1524
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1525
|
+
}
|
|
1526
|
+
function getSelfHosts() {
|
|
1527
|
+
const hosts = [];
|
|
1528
|
+
try {
|
|
1529
|
+
const hostname = os.hostname().trim();
|
|
1530
|
+
if (hostname) hosts.push(hostname);
|
|
1531
|
+
} catch {
|
|
1503
1532
|
}
|
|
1504
|
-
let
|
|
1533
|
+
let interfaces = {};
|
|
1505
1534
|
try {
|
|
1506
|
-
|
|
1535
|
+
interfaces = os.networkInterfaces();
|
|
1507
1536
|
} catch {
|
|
1508
|
-
|
|
1537
|
+
}
|
|
1538
|
+
for (const addrs of Object.values(interfaces)) {
|
|
1539
|
+
for (const addr of addrs ?? []) {
|
|
1540
|
+
if (!addr.address || addr.internal) continue;
|
|
1541
|
+
const isIPv6 = addr.family === "IPv6" || addr.family === 6;
|
|
1542
|
+
if (isIPv6) {
|
|
1543
|
+
const bare = addr.address.split("%")[0];
|
|
1544
|
+
if (bare.toLowerCase().startsWith("fe80")) continue;
|
|
1545
|
+
hosts.push(`[${bare}]`);
|
|
1546
|
+
} else {
|
|
1547
|
+
hosts.push(addr.address);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return hosts;
|
|
1552
|
+
}
|
|
1553
|
+
function buildAllowedHosts(configured = [], bindHost, selfHosts = []) {
|
|
1554
|
+
const normalizedConfigured = configured.map(normalizeHost).filter((h) => h !== null);
|
|
1555
|
+
if (normalizedConfigured.includes(ALLOW_ANY_HOST)) {
|
|
1556
|
+
return /* @__PURE__ */ new Set([ALLOW_ANY_HOST]);
|
|
1557
|
+
}
|
|
1558
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
1559
|
+
for (const h of LOOPBACK_HOSTS) {
|
|
1560
|
+
const normalized = normalizeHost(h);
|
|
1561
|
+
if (normalized) hosts.add(normalized);
|
|
1562
|
+
}
|
|
1563
|
+
const normalizedBind = bindHost ? normalizeHost(bindHost) : null;
|
|
1564
|
+
const bindIsWildcard = !normalizedBind || normalizedBind === "0.0.0.0" || normalizedBind === "[::]";
|
|
1565
|
+
if (normalizedBind && !bindIsWildcard) {
|
|
1566
|
+
hosts.add(normalizedBind);
|
|
1567
|
+
}
|
|
1568
|
+
if (bindIsWildcard) {
|
|
1569
|
+
for (const h of selfHosts) {
|
|
1570
|
+
const normalized = normalizeHost(h);
|
|
1571
|
+
if (normalized) hosts.add(normalized);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
for (const h of normalizedConfigured) hosts.add(h);
|
|
1575
|
+
return hosts;
|
|
1576
|
+
}
|
|
1577
|
+
function validateOrigin(originHeader, hostHeader, allowedHosts) {
|
|
1578
|
+
const allowAny = allowedHosts.has(ALLOW_ANY_HOST);
|
|
1579
|
+
const trimmedHost = (hostHeader ?? "").trim();
|
|
1580
|
+
if (!trimmedHost || INVALID_HOST_CHARS.test(trimmedHost)) {
|
|
1581
|
+
return { ok: false, status: 400, message: "Malformed Host header" };
|
|
1509
1582
|
}
|
|
1510
1583
|
let hostname;
|
|
1511
1584
|
try {
|
|
@@ -1516,11 +1589,32 @@ function validateOrigin(originHeader, hostHeader) {
|
|
|
1516
1589
|
if (!hostname) {
|
|
1517
1590
|
return { ok: false, status: 400, message: "Malformed Host header" };
|
|
1518
1591
|
}
|
|
1519
|
-
if (
|
|
1592
|
+
if (!allowAny && !allowedHosts.has(hostname)) {
|
|
1593
|
+
return {
|
|
1594
|
+
ok: false,
|
|
1595
|
+
status: 403,
|
|
1596
|
+
message: `Host '${hostname}' is not allowed. Only loopback is permitted by default; set --allowed-hosts (or DBHUB_ALLOWED_HOSTS) to serve other hostnames. This protects against DNS rebinding.`
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
if (originHeader === void 0) return { ok: true };
|
|
1600
|
+
const trimmedOrigin = originHeader.trim();
|
|
1601
|
+
if (!trimmedOrigin) {
|
|
1602
|
+
return { ok: false, status: 400, message: "Malformed Origin header" };
|
|
1603
|
+
}
|
|
1604
|
+
let originHostname;
|
|
1605
|
+
try {
|
|
1606
|
+
originHostname = new URL(trimmedOrigin).hostname.toLowerCase();
|
|
1607
|
+
} catch {
|
|
1608
|
+
return { ok: false, status: 400, message: "Malformed Origin header" };
|
|
1609
|
+
}
|
|
1610
|
+
if (!originHostname) {
|
|
1611
|
+
return { ok: false, status: 400, message: "Malformed Origin header" };
|
|
1612
|
+
}
|
|
1613
|
+
if (!allowAny && !allowedHosts.has(originHostname)) {
|
|
1520
1614
|
return {
|
|
1521
1615
|
ok: false,
|
|
1522
1616
|
status: 403,
|
|
1523
|
-
message:
|
|
1617
|
+
message: `Origin '${originHostname}' is not allowed`
|
|
1524
1618
|
};
|
|
1525
1619
|
}
|
|
1526
1620
|
return { ok: true };
|
|
@@ -1578,7 +1672,7 @@ See documentation for more details on configuring database connections.
|
|
|
1578
1672
|
const sources = sourceConfigsData.sources;
|
|
1579
1673
|
console.error(`Configuration source: ${sourceConfigsData.source}`);
|
|
1580
1674
|
await connectorManager.connectWithSources(sources);
|
|
1581
|
-
const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-
|
|
1675
|
+
const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-IPRZ55YC.js");
|
|
1582
1676
|
initializeToolRegistry2({
|
|
1583
1677
|
sources: sourceConfigsData.sources,
|
|
1584
1678
|
tools: sourceConfigsData.tools
|
|
@@ -1599,6 +1693,7 @@ See documentation for more details on configuring database connections.
|
|
|
1599
1693
|
const transportData = resolveTransport();
|
|
1600
1694
|
const port = transportData.type === "http" ? resolvePort().port : null;
|
|
1601
1695
|
const host = transportData.type === "http" ? resolveHost().host : null;
|
|
1696
|
+
const allowedHosts = transportData.type === "http" ? buildAllowedHosts(resolveAllowedHosts().hosts, host ?? void 0, getSelfHosts()) : /* @__PURE__ */ new Set();
|
|
1602
1697
|
const activeModes = [];
|
|
1603
1698
|
const modeDescriptions = [];
|
|
1604
1699
|
const isDemo = isDemoMode();
|
|
@@ -1624,7 +1719,7 @@ See documentation for more details on configuring database connections.
|
|
|
1624
1719
|
app.use(express.json());
|
|
1625
1720
|
app.use((req, res, next) => {
|
|
1626
1721
|
const origin = req.headers.origin;
|
|
1627
|
-
const result = validateOrigin(origin, req.headers.host);
|
|
1722
|
+
const result = validateOrigin(origin, req.headers.host, allowedHosts);
|
|
1628
1723
|
if (!result.ok) {
|
|
1629
1724
|
return res.status(result.status).json({
|
|
1630
1725
|
error: result.status === 400 ? "Bad Request" : "Forbidden",
|
|
@@ -1690,6 +1785,11 @@ See documentation for more details on configuring database connections.
|
|
|
1690
1785
|
const displayHost = boundHost.includes(":") ? `[${boundHost}]` : boundHost;
|
|
1691
1786
|
const userHost = boundHost === "0.0.0.0" || boundHost === "::" ? "localhost" : displayHost;
|
|
1692
1787
|
console.error(`HTTP server listening on ${displayHost}:${boundPort}`);
|
|
1788
|
+
if (allowedHosts.has(ALLOW_ANY_HOST)) {
|
|
1789
|
+
console.error("Allowed hosts: * (DNS-rebinding protection DISABLED \u2014 ensure DBHub is fronted by your own auth/proxy)");
|
|
1790
|
+
} else {
|
|
1791
|
+
console.error(`Allowed hosts: ${[...allowedHosts].join(", ")} (set --allowed-hosts to serve other hostnames)`);
|
|
1792
|
+
}
|
|
1693
1793
|
if (process.env.NODE_ENV === "development") {
|
|
1694
1794
|
console.error("Development mode detected!");
|
|
1695
1795
|
console.error(" Workbench dev server (with HMR): http://localhost:5173");
|
|
@@ -1726,11 +1826,11 @@ See documentation for more details on configuring database connections.
|
|
|
1726
1826
|
|
|
1727
1827
|
// src/index.ts
|
|
1728
1828
|
var connectorModules = [
|
|
1729
|
-
{ load: () => import("./postgres-
|
|
1730
|
-
{ load: () => import("./sqlserver-
|
|
1731
|
-
{ load: () => import("./sqlite-
|
|
1732
|
-
{ load: () => import("./mysql-
|
|
1733
|
-
{ load: () => import("./mariadb-
|
|
1829
|
+
{ load: () => import("./postgres-BKBCGDZS.js"), name: "PostgreSQL", driver: "pg" },
|
|
1830
|
+
{ load: () => import("./sqlserver-47APXMSF.js"), name: "SQL Server", driver: "mssql" },
|
|
1831
|
+
{ load: () => import("./sqlite-CQ4LCBB6.js"), name: "SQLite", driver: "node:sqlite" },
|
|
1832
|
+
{ load: () => import("./mysql-EK6TCER5.js"), name: "MySQL", driver: "mysql2" },
|
|
1833
|
+
{ load: () => import("./mariadb-JPEKVFEW.js"), name: "MariaDB", driver: "mariadb" }
|
|
1734
1834
|
];
|
|
1735
1835
|
loadConnectors(connectorModules).then(() => main()).catch((error) => {
|
|
1736
1836
|
console.error("Fatal error:", error);
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
} from "./chunk-JFWX35TB.js";
|
|
8
8
|
import {
|
|
9
9
|
SQLRowLimiter
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BXXHWEQS.js";
|
|
11
11
|
import {
|
|
12
12
|
ConnectorRegistry,
|
|
13
13
|
SafeURL,
|
|
14
14
|
obfuscateDSNPassword,
|
|
15
15
|
splitSQLStatements
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JEZZN2YZ.js";
|
|
17
17
|
|
|
18
18
|
// src/connectors/mariadb/index.ts
|
|
19
19
|
import * as mariadb from "mariadb";
|
|
@@ -466,6 +466,9 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
466
466
|
}
|
|
467
467
|
const conn = await this.pool.getConnection();
|
|
468
468
|
try {
|
|
469
|
+
if (options.readonly) {
|
|
470
|
+
await conn.query("START TRANSACTION READ ONLY");
|
|
471
|
+
}
|
|
469
472
|
let processedSQL = sql;
|
|
470
473
|
if (options.maxRows) {
|
|
471
474
|
const statements = splitSQLStatements(sql, "mariadb");
|
|
@@ -492,8 +495,17 @@ var MariaDBConnector = class _MariaDBConnector {
|
|
|
492
495
|
}
|
|
493
496
|
const rows = parseQueryResults(results);
|
|
494
497
|
const rowCount = extractAffectedRows(results);
|
|
498
|
+
if (options.readonly) {
|
|
499
|
+
await conn.query("COMMIT");
|
|
500
|
+
}
|
|
495
501
|
return { rows, rowCount };
|
|
496
502
|
} catch (error) {
|
|
503
|
+
if (options.readonly) {
|
|
504
|
+
try {
|
|
505
|
+
await conn.query("ROLLBACK");
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
}
|
|
497
509
|
console.error("Error executing query:", error);
|
|
498
510
|
throw error;
|
|
499
511
|
} finally {
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
} from "./chunk-JFWX35TB.js";
|
|
8
8
|
import {
|
|
9
9
|
SQLRowLimiter
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BXXHWEQS.js";
|
|
11
11
|
import {
|
|
12
12
|
ConnectorRegistry,
|
|
13
13
|
SafeURL,
|
|
14
14
|
obfuscateDSNPassword,
|
|
15
15
|
splitSQLStatements
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JEZZN2YZ.js";
|
|
17
17
|
|
|
18
18
|
// src/connectors/mysql/index.ts
|
|
19
19
|
import mysql from "mysql2/promise";
|
|
@@ -472,6 +472,9 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
472
472
|
}
|
|
473
473
|
const conn = await this.pool.getConnection();
|
|
474
474
|
try {
|
|
475
|
+
if (options.readonly) {
|
|
476
|
+
await conn.query("START TRANSACTION READ ONLY");
|
|
477
|
+
}
|
|
475
478
|
let processedSQL = sql;
|
|
476
479
|
if (options.maxRows) {
|
|
477
480
|
const statements = splitSQLStatements(sql, "mysql");
|
|
@@ -499,8 +502,17 @@ var MySQLConnector = class _MySQLConnector {
|
|
|
499
502
|
const [firstResult] = results;
|
|
500
503
|
const rows = parseQueryResults(firstResult);
|
|
501
504
|
const rowCount = extractAffectedRows(firstResult);
|
|
505
|
+
if (options.readonly) {
|
|
506
|
+
await conn.query("COMMIT");
|
|
507
|
+
}
|
|
502
508
|
return { rows, rowCount };
|
|
503
509
|
} catch (error) {
|
|
510
|
+
if (options.readonly) {
|
|
511
|
+
try {
|
|
512
|
+
await conn.query("ROLLBACK");
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
}
|
|
504
516
|
console.error("Error executing query:", error);
|
|
505
517
|
throw error;
|
|
506
518
|
} finally {
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-JFWX35TB.js";
|
|
4
4
|
import {
|
|
5
5
|
SQLRowLimiter
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BXXHWEQS.js";
|
|
7
7
|
import {
|
|
8
8
|
ConnectorRegistry,
|
|
9
9
|
SafeURL,
|
|
10
10
|
obfuscateDSNPassword,
|
|
11
11
|
splitSQLStatements
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JEZZN2YZ.js";
|
|
13
13
|
|
|
14
14
|
// src/connectors/postgres/index.ts
|
|
15
15
|
import fs from "fs";
|
|
@@ -491,6 +491,25 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
491
491
|
const statements = splitSQLStatements(sql, "postgres");
|
|
492
492
|
if (statements.length === 1) {
|
|
493
493
|
const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
|
|
494
|
+
if (options.readonly) {
|
|
495
|
+
await client.query("BEGIN READ ONLY");
|
|
496
|
+
try {
|
|
497
|
+
const result2 = parameters && parameters.length > 0 ? await client.query(processedStatement, parameters) : await client.query(processedStatement);
|
|
498
|
+
await client.query("COMMIT");
|
|
499
|
+
return { rows: result2.rows, rowCount: result2.rowCount ?? result2.rows.length };
|
|
500
|
+
} catch (error) {
|
|
501
|
+
try {
|
|
502
|
+
await client.query("ROLLBACK");
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
console.error(`[PostgreSQL executeSQL] ERROR: ${error.message}`);
|
|
506
|
+
console.error(`[PostgreSQL executeSQL] SQL: ${processedStatement}`);
|
|
507
|
+
if (parameters && parameters.length > 0) {
|
|
508
|
+
console.error(`[PostgreSQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
|
|
509
|
+
}
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
494
513
|
let result;
|
|
495
514
|
if (parameters && parameters.length > 0) {
|
|
496
515
|
try {
|
|
@@ -511,7 +530,7 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
511
530
|
}
|
|
512
531
|
let allRows = [];
|
|
513
532
|
let totalRowCount = 0;
|
|
514
|
-
await client.query("BEGIN");
|
|
533
|
+
await client.query(options.readonly ? "BEGIN READ ONLY" : "BEGIN");
|
|
515
534
|
try {
|
|
516
535
|
for (let statement of statements) {
|
|
517
536
|
const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
|
|
@@ -525,7 +544,10 @@ var PostgresConnector = class _PostgresConnector {
|
|
|
525
544
|
}
|
|
526
545
|
await client.query("COMMIT");
|
|
527
546
|
} catch (error) {
|
|
528
|
-
|
|
547
|
+
try {
|
|
548
|
+
await client.query("ROLLBACK");
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
529
551
|
throw error;
|
|
530
552
|
}
|
|
531
553
|
return { rows: allRows, rowCount: totalRowCount };
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
ToolRegistry,
|
|
3
3
|
getToolRegistry,
|
|
4
4
|
initializeToolRegistry
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WPXPKRDU.js";
|
|
6
6
|
import "./chunk-WVVMH6FJ.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-JEZZN2YZ.js";
|
|
8
8
|
export {
|
|
9
9
|
ToolRegistry,
|
|
10
10
|
getToolRegistry,
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-JFWX35TB.js";
|
|
4
4
|
import {
|
|
5
5
|
SQLRowLimiter
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BXXHWEQS.js";
|
|
7
7
|
import {
|
|
8
8
|
ConnectorRegistry,
|
|
9
9
|
SafeURL,
|
|
10
10
|
obfuscateDSNPassword,
|
|
11
11
|
splitSQLStatements
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JEZZN2YZ.js";
|
|
13
13
|
|
|
14
14
|
// src/connectors/sqlite/suppress-experimental-warning.ts
|
|
15
15
|
var installed = false;
|
|
@@ -319,6 +319,9 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
319
319
|
if (!this.db) {
|
|
320
320
|
throw new Error("Not connected to SQLite database");
|
|
321
321
|
}
|
|
322
|
+
if (options.readonly) {
|
|
323
|
+
this.db.exec("PRAGMA query_only = ON");
|
|
324
|
+
}
|
|
322
325
|
try {
|
|
323
326
|
const statements = splitSQLStatements(sql, "sqlite");
|
|
324
327
|
if (statements.length === 1) {
|
|
@@ -375,19 +378,30 @@ var SQLiteConnector = class _SQLiteConnector {
|
|
|
375
378
|
}
|
|
376
379
|
let totalChanges = 0;
|
|
377
380
|
for (const statement of writeStatements) {
|
|
381
|
+
if (options.readonly) {
|
|
382
|
+
this.db.exec("PRAGMA query_only = ON");
|
|
383
|
+
}
|
|
378
384
|
const result = this.prepare(statement).run();
|
|
379
385
|
totalChanges += Number(result.changes);
|
|
380
386
|
}
|
|
381
387
|
let allRows = [];
|
|
382
388
|
for (let statement of readStatements) {
|
|
389
|
+
if (options.readonly) {
|
|
390
|
+
this.db.exec("PRAGMA query_only = ON");
|
|
391
|
+
}
|
|
383
392
|
statement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
|
|
384
393
|
const result = this.prepare(statement).all();
|
|
385
394
|
allRows.push(...result);
|
|
386
395
|
}
|
|
387
396
|
return { rows: allRows, rowCount: totalChanges + allRows.length };
|
|
388
397
|
}
|
|
389
|
-
}
|
|
390
|
-
|
|
398
|
+
} finally {
|
|
399
|
+
if (options.readonly) {
|
|
400
|
+
try {
|
|
401
|
+
this.db.exec("PRAGMA query_only = OFF");
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
}
|
|
391
405
|
}
|
|
392
406
|
}
|
|
393
407
|
};
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-WVVMH6FJ.js";
|
|
4
4
|
import {
|
|
5
5
|
SQLRowLimiter
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BXXHWEQS.js";
|
|
7
7
|
import {
|
|
8
8
|
ConnectorRegistry,
|
|
9
9
|
SafeURL,
|
|
10
10
|
obfuscateDSNPassword,
|
|
11
11
|
stripCommentsAndStrings
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JEZZN2YZ.js";
|
|
13
13
|
|
|
14
14
|
// src/connectors/sqlserver/index.ts
|
|
15
15
|
import sql from "mssql";
|
package/package.json
CHANGED