@zz1996/dbhub-dameng 1.2.5-dameng.621 → 1.3.0-dameng.631
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/dist/{chunk-HGU7ILEZ.js → chunk-7OHPTFUJ.js} +73 -58
- package/dist/{chunk-2JL4KCP5.js → chunk-AQKEKC24.js} +21 -1
- package/dist/{chunk-O3PEWKBB.js → chunk-CJ5QQT7D.js} +43 -2
- package/dist/{chunk-3RJRH3CN.js → chunk-GDZT5PH3.js} +9 -3
- package/dist/{chunk-HDPZAIDL.js → chunk-JKYVOIJG.js} +34 -5
- package/dist/{chunk-4276LPPU.js → chunk-PVBTULUE.js} +1 -1
- package/dist/{chunk-JFWX35TB.js → chunk-X2EC47W4.js} +1 -0
- package/dist/{dameng-NKF7NZKE.js → dameng-DTZZQBDP.js} +3 -3
- package/dist/index.js +13 -12
- package/dist/{mariadb-MMC6WMEG.js → mariadb-3RX7DLHI.js} +6 -6
- package/dist/{mysql-TESH7KKV.js → mysql-XL7JXXWL.js} +6 -6
- package/dist/oracle-7KQQDGH7.js +756 -0
- package/dist/{postgres-C24SBQ4I.js → postgres-KO5JB4XV.js} +6 -6
- package/dist/public/assets/{index-BdBaFSHd.js → index-BRlIJaqn.js} +27 -27
- package/dist/public/index.html +1 -1
- package/dist/{registry-WVSFVS35.js → registry-OYHTCTSH.js} +3 -3
- package/dist/{sqlite-7BNGDMBZ.js → sqlite-OSP72VCJ.js} +5 -5
- package/dist/{sqlserver-KZPPK67Q.js → sqlserver-OQFUEISM.js} +6 -14
- package/package.json +2 -1
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
getDatabaseTypeFromDSN,
|
|
6
6
|
getDefaultPortForType,
|
|
7
|
+
obfuscateDSNPassword,
|
|
7
8
|
parseConnectionInfoFromDSN
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GDZT5PH3.js";
|
|
9
10
|
import {
|
|
10
11
|
ConnectorRegistry,
|
|
11
12
|
SafeURL,
|
|
12
13
|
stripCommentsAndStrings
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-AQKEKC24.js";
|
|
14
15
|
|
|
15
16
|
// src/tools/builtin-tools.ts
|
|
16
17
|
var BUILTIN_TOOL_EXECUTE_SQL = "execute_sql";
|
|
@@ -662,7 +663,7 @@ function buildDSNFromEnvParams() {
|
|
|
662
663
|
return null;
|
|
663
664
|
}
|
|
664
665
|
}
|
|
665
|
-
const supportedTypes = ["postgres", "postgresql", "mysql", "mariadb", "sqlserver", "sqlite", "dameng"];
|
|
666
|
+
const supportedTypes = ["postgres", "postgresql", "mysql", "mariadb", "sqlserver", "sqlite", "oracle", "dameng"];
|
|
666
667
|
if (!supportedTypes.includes(dbType.toLowerCase())) {
|
|
667
668
|
throw new Error(`Unsupported DB_TYPE: ${dbType}. Supported types: ${supportedTypes.join(", ")}`);
|
|
668
669
|
}
|
|
@@ -680,6 +681,9 @@ function buildDSNFromEnvParams() {
|
|
|
680
681
|
case "sqlserver":
|
|
681
682
|
port = "1433";
|
|
682
683
|
break;
|
|
684
|
+
case "oracle":
|
|
685
|
+
port = "1521";
|
|
686
|
+
break;
|
|
683
687
|
case "dameng":
|
|
684
688
|
port = "5236";
|
|
685
689
|
break;
|
|
@@ -826,15 +830,7 @@ function splitTokenList(value) {
|
|
|
826
830
|
return value.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
827
831
|
}
|
|
828
832
|
function redactDSN(dsn) {
|
|
829
|
-
|
|
830
|
-
const url = new URL(dsn);
|
|
831
|
-
if (url.password) {
|
|
832
|
-
url.password = "*******";
|
|
833
|
-
}
|
|
834
|
-
return url.toString();
|
|
835
|
-
} catch (error) {
|
|
836
|
-
return dsn.replace(/\/\/([^:]+):([^@]+)@/, "//$1:***@");
|
|
837
|
-
}
|
|
833
|
+
return obfuscateDSNPassword(dsn);
|
|
838
834
|
}
|
|
839
835
|
function resolveId() {
|
|
840
836
|
const args = parseCommandLineArgs();
|
|
@@ -981,7 +977,7 @@ async function resolveSourceConfigs() {
|
|
|
981
977
|
dsnUrl = new SafeURL(dsnResult.dsn);
|
|
982
978
|
} catch (error) {
|
|
983
979
|
throw new Error(
|
|
984
|
-
`Invalid DSN format: ${dsnResult.dsn}. Expected format: protocol://[user[:password]@]host[:port]/database`
|
|
980
|
+
`Invalid DSN format: ${redactDSN(dsnResult.dsn)}. Expected format: protocol://[user[:password]@]host[:port]/database`
|
|
985
981
|
);
|
|
986
982
|
}
|
|
987
983
|
const protocol = dsnUrl.protocol.replace(":", "");
|
|
@@ -998,6 +994,8 @@ async function resolveSourceConfigs() {
|
|
|
998
994
|
dbType = "dameng";
|
|
999
995
|
} else if (protocol === "sqlite") {
|
|
1000
996
|
dbType = "sqlite";
|
|
997
|
+
} else if (protocol === "oracle") {
|
|
998
|
+
dbType = "oracle";
|
|
1001
999
|
} else {
|
|
1002
1000
|
throw new Error(`Unsupported database type in DSN: ${protocol}`);
|
|
1003
1001
|
}
|
|
@@ -1295,7 +1293,7 @@ function validateSourceConfig(source, configPath) {
|
|
|
1295
1293
|
);
|
|
1296
1294
|
}
|
|
1297
1295
|
if (source.type) {
|
|
1298
|
-
const validTypes = ["postgres", "mysql", "mariadb", "sqlserver", "sqlite", "dameng"];
|
|
1296
|
+
const validTypes = ["postgres", "mysql", "mariadb", "sqlserver", "sqlite", "oracle", "dameng"];
|
|
1299
1297
|
if (!validTypes.includes(source.type)) {
|
|
1300
1298
|
throw new Error(
|
|
1301
1299
|
`Configuration file ${configPath}: source '${source.id}' has invalid type '${source.type}'. Valid types: ${validTypes.join(", ")}`
|
|
@@ -1384,9 +1382,14 @@ function validateSourceConfig(source, configPath) {
|
|
|
1384
1382
|
`Configuration file ${configPath}: source '${source.id}' has invalid sslmode '${source.sslmode}'. Valid values: ${validSslModes.join(", ")}`
|
|
1385
1383
|
);
|
|
1386
1384
|
}
|
|
1387
|
-
|
|
1385
|
+
const verifyModesByType = {
|
|
1386
|
+
postgres: ["verify-ca", "verify-full"],
|
|
1387
|
+
oracle: ["verify-full"]
|
|
1388
|
+
};
|
|
1389
|
+
if ((source.sslmode === "verify-ca" || source.sslmode === "verify-full") && !(verifyModesByType[source.type] ?? []).includes(source.sslmode)) {
|
|
1390
|
+
const supported = ["disable", "require", ...verifyModesByType[source.type] ?? []];
|
|
1388
1391
|
throw new Error(
|
|
1389
|
-
`Configuration file ${configPath}: source '${source.id}' has sslmode '${source.sslmode}' which is
|
|
1392
|
+
`Configuration file ${configPath}: source '${source.id}' has sslmode '${source.sslmode}' which is not supported for ${source.type}. Valid values for ${source.type}: ${supported.join(", ")}`
|
|
1390
1393
|
);
|
|
1391
1394
|
}
|
|
1392
1395
|
}
|
|
@@ -1394,6 +1397,11 @@ function validateSourceConfig(source, configPath) {
|
|
|
1394
1397
|
validateDSNFieldConflicts(source, configPath);
|
|
1395
1398
|
}
|
|
1396
1399
|
if (source.sslrootcert !== void 0) {
|
|
1400
|
+
if (source.type !== "postgres") {
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
`Configuration file ${configPath}: source '${source.id}' has sslrootcert but it is only supported for PostgreSQL.`
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1397
1405
|
if (source.sslmode !== "verify-ca" && source.sslmode !== "verify-full") {
|
|
1398
1406
|
throw new Error(
|
|
1399
1407
|
`Configuration file ${configPath}: source '${source.id}' has sslrootcert but sslmode is '${source.sslmode ?? "not set"}'. sslrootcert requires sslmode 'verify-ca' or 'verify-full'`
|
|
@@ -1746,11 +1754,17 @@ var NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
|
1746
1754
|
"ENETUNREACH",
|
|
1747
1755
|
"ECONNRESET"
|
|
1748
1756
|
]);
|
|
1757
|
+
var NETWORK_CODES_BY_TYPE = {
|
|
1758
|
+
// NJS-503: "connection to host ... could not be established" (Thin mode)
|
|
1759
|
+
oracle: /* @__PURE__ */ new Set(["NJS-503"])
|
|
1760
|
+
};
|
|
1749
1761
|
var AUTH_CODES = {
|
|
1750
1762
|
postgres: ["28P01", "28000"],
|
|
1751
1763
|
mysql: ["ER_ACCESS_DENIED_ERROR", 1045, 1698],
|
|
1752
1764
|
mariadb: ["ER_ACCESS_DENIED_ERROR", 1045, 1698],
|
|
1753
1765
|
sqlserver: ["ELOGIN"],
|
|
1766
|
+
// ORA-01017: invalid username/password; ORA-28000: account locked
|
|
1767
|
+
oracle: ["ORA-01017", "ORA-28000"],
|
|
1754
1768
|
dameng: [-2501],
|
|
1755
1769
|
// Invalid username or password
|
|
1756
1770
|
sqlite: []
|
|
@@ -1774,7 +1788,7 @@ function classifyConnectionError(error, connectorType, sourceId) {
|
|
|
1774
1788
|
return { code: "TUNNEL_FAILED", message: tunnelMessage(sourceId) };
|
|
1775
1789
|
}
|
|
1776
1790
|
const code = err.code;
|
|
1777
|
-
if (typeof code === "string" && NETWORK_CODES.has(code)) {
|
|
1791
|
+
if (typeof code === "string" && (NETWORK_CODES.has(code) || NETWORK_CODES_BY_TYPE[connectorType]?.has(code))) {
|
|
1778
1792
|
return { code: "SOURCE_UNREACHABLE", message: unreachableMessage(sourceId) };
|
|
1779
1793
|
}
|
|
1780
1794
|
const authCodes = AUTH_CODES[connectorType];
|
|
@@ -2272,24 +2286,29 @@ var PARAMETER_STYLES = {
|
|
|
2272
2286
|
// ?, ?, ?
|
|
2273
2287
|
sqlserver: "named",
|
|
2274
2288
|
// @p1, @p2, @p3
|
|
2275
|
-
sqlite: "positional"
|
|
2289
|
+
sqlite: "positional",
|
|
2276
2290
|
// ?, ?, ?
|
|
2291
|
+
oracle: "colon"
|
|
2292
|
+
// :1, :2, :3
|
|
2277
2293
|
};
|
|
2278
|
-
function detectParameterStyle(statement) {
|
|
2279
|
-
const cleanedSQL = stripCommentsAndStrings(statement);
|
|
2294
|
+
function detectParameterStyle(statement, connectorType) {
|
|
2295
|
+
const cleanedSQL = stripCommentsAndStrings(statement, connectorType);
|
|
2280
2296
|
if (/\$\d+/.test(cleanedSQL)) {
|
|
2281
2297
|
return "numbered";
|
|
2282
2298
|
}
|
|
2283
2299
|
if (/@p\d+/.test(cleanedSQL)) {
|
|
2284
2300
|
return "named";
|
|
2285
2301
|
}
|
|
2302
|
+
if (/(?<!:):\d+/.test(cleanedSQL)) {
|
|
2303
|
+
return "colon";
|
|
2304
|
+
}
|
|
2286
2305
|
if (/\?/.test(cleanedSQL)) {
|
|
2287
2306
|
return "positional";
|
|
2288
2307
|
}
|
|
2289
2308
|
return "none";
|
|
2290
2309
|
}
|
|
2291
2310
|
function validateParameterStyle(statement, connectorType) {
|
|
2292
|
-
const detectedStyle = detectParameterStyle(statement);
|
|
2311
|
+
const detectedStyle = detectParameterStyle(statement, connectorType);
|
|
2293
2312
|
const expectedStyle = PARAMETER_STYLES[connectorType];
|
|
2294
2313
|
if (detectedStyle === "none") {
|
|
2295
2314
|
return;
|
|
@@ -2298,57 +2317,53 @@ function validateParameterStyle(statement, connectorType) {
|
|
|
2298
2317
|
const examples = {
|
|
2299
2318
|
numbered: "$1, $2, $3",
|
|
2300
2319
|
positional: "?, ?, ?",
|
|
2301
|
-
named: "@p1, @p2, @p3"
|
|
2320
|
+
named: "@p1, @p2, @p3",
|
|
2321
|
+
colon: ":1, :2, :3"
|
|
2302
2322
|
};
|
|
2303
2323
|
throw new Error(
|
|
2304
2324
|
`Invalid parameter syntax for ${connectorType}. Expected ${expectedStyle} style (${examples[expectedStyle]}), but found ${detectedStyle} style in statement.`
|
|
2305
2325
|
);
|
|
2306
2326
|
}
|
|
2307
2327
|
}
|
|
2308
|
-
function countParameters(statement) {
|
|
2309
|
-
const style = detectParameterStyle(statement);
|
|
2310
|
-
const cleanedSQL = stripCommentsAndStrings(statement);
|
|
2328
|
+
function countParameters(statement, connectorType) {
|
|
2329
|
+
const style = detectParameterStyle(statement, connectorType);
|
|
2330
|
+
const cleanedSQL = stripCommentsAndStrings(statement, connectorType);
|
|
2311
2331
|
switch (style) {
|
|
2312
|
-
case "numbered":
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
if (!uniqueIndices.includes(i)) {
|
|
2320
|
-
throw new Error(
|
|
2321
|
-
`Non-sequential numbered parameters detected. Found placeholders: ${uniqueIndices.map((n) => `$${n}`).join(", ")}. Parameters must be sequential starting from $1 (missing $${i}).`
|
|
2322
|
-
);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
return maxIndex;
|
|
2326
|
-
}
|
|
2327
|
-
case "named": {
|
|
2328
|
-
const matches = cleanedSQL.match(/@p\d+/g);
|
|
2329
|
-
if (!matches) return 0;
|
|
2330
|
-
const numbers = matches.map((m) => parseInt(m.slice(2), 10));
|
|
2331
|
-
const uniqueIndices = Array.from(new Set(numbers)).sort((a, b) => a - b);
|
|
2332
|
-
const maxIndex = Math.max(...uniqueIndices);
|
|
2333
|
-
for (let i = 1; i <= maxIndex; i++) {
|
|
2334
|
-
if (!uniqueIndices.includes(i)) {
|
|
2335
|
-
throw new Error(
|
|
2336
|
-
`Non-sequential named parameters detected. Found placeholders: ${uniqueIndices.map((n) => `@p${n}`).join(", ")}. Parameters must be sequential starting from @p1 (missing @p${i}).`
|
|
2337
|
-
);
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2340
|
-
return maxIndex;
|
|
2341
|
-
}
|
|
2342
|
-
case "positional": {
|
|
2332
|
+
case "numbered":
|
|
2333
|
+
return countIndexedParameters(cleanedSQL, /\$\d+/g, "$", "numbered");
|
|
2334
|
+
case "named":
|
|
2335
|
+
return countIndexedParameters(cleanedSQL, /@p\d+/g, "@p", "named");
|
|
2336
|
+
case "colon":
|
|
2337
|
+
return countIndexedParameters(cleanedSQL, /(?<!:):\d+/g, ":", "colon-numbered");
|
|
2338
|
+
case "positional":
|
|
2343
2339
|
return (cleanedSQL.match(/\?/g) || []).length;
|
|
2344
|
-
}
|
|
2345
2340
|
default:
|
|
2346
2341
|
return 0;
|
|
2347
2342
|
}
|
|
2348
2343
|
}
|
|
2344
|
+
function countIndexedParameters(cleanedSQL, pattern, sigil, styleName) {
|
|
2345
|
+
const matches = cleanedSQL.match(pattern);
|
|
2346
|
+
if (!matches) return 0;
|
|
2347
|
+
const indices = new Set(matches.map((m) => parseInt(m.slice(sigil.length), 10)));
|
|
2348
|
+
if (indices.has(0)) {
|
|
2349
|
+
throw new Error(
|
|
2350
|
+
`Invalid ${styleName} parameter ${sigil}0. Parameters must be sequential starting from ${sigil}1.`
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
const maxIndex = Math.max(...indices);
|
|
2354
|
+
for (let i = 1; i <= maxIndex; i++) {
|
|
2355
|
+
if (!indices.has(i)) {
|
|
2356
|
+
const found = Array.from(indices).sort((a, b) => a - b).map((n) => `${sigil}${n}`).join(", ");
|
|
2357
|
+
throw new Error(
|
|
2358
|
+
`Non-sequential ${styleName} parameters detected. Found placeholders: ${found}. Parameters must be sequential starting from ${sigil}1 (missing ${sigil}${i}).`
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return maxIndex;
|
|
2363
|
+
}
|
|
2349
2364
|
function validateParameters(statement, parameters, connectorType) {
|
|
2350
2365
|
validateParameterStyle(statement, connectorType);
|
|
2351
|
-
const paramCount = countParameters(statement);
|
|
2366
|
+
const paramCount = countParameters(statement, connectorType);
|
|
2352
2367
|
const definedCount = parameters?.length || 0;
|
|
2353
2368
|
if (paramCount !== definedCount) {
|
|
2354
2369
|
throw new Error(
|
|
@@ -320,13 +320,31 @@ function scanTokenSQLite(sql, i) {
|
|
|
320
320
|
function scanTokenSQLServer(sql, i) {
|
|
321
321
|
return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBracketQuotedIdentifier(sql, i) ?? plainToken(i);
|
|
322
322
|
}
|
|
323
|
+
function scanOracleAlternativeQuotedString(sql, i) {
|
|
324
|
+
if (sql[i] !== "q" && sql[i] !== "Q" || sql[i + 1] !== "'") {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const open = sql[i + 2];
|
|
328
|
+
if (open === void 0 || open === " " || open === " " || open === "\n") {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const mirrors = { "(": ")", "[": "]", "{": "}", "<": ">" };
|
|
332
|
+
const close = (mirrors[open] ?? open) + "'";
|
|
333
|
+
const closeIdx = sql.indexOf(close, i + 3);
|
|
334
|
+
const end = closeIdx !== -1 ? closeIdx + close.length : sql.length;
|
|
335
|
+
return { type: TokenType.QuotedBlock, end };
|
|
336
|
+
}
|
|
337
|
+
function scanTokenOracle(sql, i) {
|
|
338
|
+
return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanOracleAlternativeQuotedString(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? plainToken(i);
|
|
339
|
+
}
|
|
323
340
|
var dialectScanners = {
|
|
324
341
|
dameng: scanTokenAnsi,
|
|
325
342
|
postgres: scanTokenPostgres,
|
|
326
343
|
mysql: scanTokenMySQL,
|
|
327
344
|
mariadb: scanTokenMySQL,
|
|
328
345
|
sqlite: scanTokenSQLite,
|
|
329
|
-
sqlserver: scanTokenSQLServer
|
|
346
|
+
sqlserver: scanTokenSQLServer,
|
|
347
|
+
oracle: scanTokenOracle
|
|
330
348
|
};
|
|
331
349
|
function getScanner(dialect) {
|
|
332
350
|
return dialect ? dialectScanners[dialect] ?? scanTokenAnsi : scanTokenAnsi;
|
|
@@ -371,6 +389,7 @@ function blankCommentsAndStrings(sql, dialect) {
|
|
|
371
389
|
}
|
|
372
390
|
return result;
|
|
373
391
|
}
|
|
392
|
+
var LEADING_SQL_NOISE = /^(?:\s+|--[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)*/;
|
|
374
393
|
function splitSQLStatements(sql, dialect) {
|
|
375
394
|
const scanToken = getScanner(dialect);
|
|
376
395
|
const statements = [];
|
|
@@ -401,5 +420,6 @@ export {
|
|
|
401
420
|
SafeURL,
|
|
402
421
|
stripCommentsAndStrings,
|
|
403
422
|
blankCommentsAndStrings,
|
|
423
|
+
LEADING_SQL_NOISE,
|
|
404
424
|
splitSQLStatements
|
|
405
425
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasMutatingKeyword
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JKYVOIJG.js";
|
|
4
4
|
import {
|
|
5
5
|
blankCommentsAndStrings
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-AQKEKC24.js";
|
|
7
7
|
|
|
8
8
|
// src/utils/sql-row-limiter.ts
|
|
9
9
|
var SQLRowLimiter = class {
|
|
@@ -227,6 +227,47 @@ LIMIT ${maxRows}${semicolon}`;
|
|
|
227
227
|
}
|
|
228
228
|
return this.applyTopToQuery(sql, maxRows);
|
|
229
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Apply maxRows to a row-returning query using Oracle's row-limiting clause.
|
|
232
|
+
*
|
|
233
|
+
* The statement is always wrapped in an inline view and capped from the
|
|
234
|
+
* outside: `SELECT * FROM (<sql>) FETCH FIRST n ROWS ONLY`. Wrapping rather
|
|
235
|
+
* than splicing keeps every shape correct with one rule - a set operation
|
|
236
|
+
* (UNION & co.) is capped as a whole, a trailing ORDER BY stays inside the
|
|
237
|
+
* view where Oracle honours it, and a query that already has its own
|
|
238
|
+
* `FETCH FIRST` / `ROWNUM` cap simply returns the smaller of the two. Oracle
|
|
239
|
+
* also accepts a leading CTE (subquery factoring) inside an inline view, so
|
|
240
|
+
* `WITH ... SELECT` needs no special casing.
|
|
241
|
+
*/
|
|
242
|
+
static applyMaxRowsForOracle(sql, maxRows) {
|
|
243
|
+
if (!maxRows || !this.isSelectQuery(sql, "oracle") || this.hasOracleForUpdate(sql)) {
|
|
244
|
+
return sql;
|
|
245
|
+
}
|
|
246
|
+
const { sql: sqlWithoutSemicolon } = trimSemicolon(sql);
|
|
247
|
+
return `SELECT * FROM (${sqlWithoutSemicolon}
|
|
248
|
+
) FETCH FIRST ${maxRows} ROWS ONLY`;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* A statement-level `FOR UPDATE` (locking read). Oracle allows neither
|
|
252
|
+
* `FOR UPDATE` inside an inline view nor a row-limiting clause alongside
|
|
253
|
+
* it, so such a statement cannot be capped and is left as written.
|
|
254
|
+
*/
|
|
255
|
+
static hasOracleForUpdate(sql) {
|
|
256
|
+
return this.findTopLevelMatch(sql, /\(|\)|\bfor\s+update\b/gi, "first", "oracle") !== null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Oracle variant of applyMaxRowsWithTruncationProbe. Because the cap always
|
|
260
|
+
* wraps (see applyMaxRowsForOracle), the probe is applied to every
|
|
261
|
+
* row-returning statement; a query whose own cap is tighter than maxRows
|
|
262
|
+
* returns fewer than maxRows + 1 rows and is therefore never flagged.
|
|
263
|
+
* A `FOR UPDATE` statement is never capped (see hasOracleForUpdate).
|
|
264
|
+
*/
|
|
265
|
+
static applyMaxRowsForOracleWithTruncationProbe(sql, maxRows) {
|
|
266
|
+
if (!maxRows || !this.isSelectQuery(sql, "oracle") || this.hasOracleForUpdate(sql)) {
|
|
267
|
+
return { sql, probeApplied: false };
|
|
268
|
+
}
|
|
269
|
+
return { sql: this.applyMaxRowsForOracle(sql, maxRows + 1), probeApplied: true };
|
|
270
|
+
}
|
|
230
271
|
/**
|
|
231
272
|
* Like applyMaxRows, but requests one probe row (maxRows + 1) whenever the
|
|
232
273
|
* cap is the binding constraint, so callers can detect truncation exactly
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SafeURL
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AQKEKC24.js";
|
|
4
4
|
|
|
5
5
|
// src/utils/dsn-obfuscate.ts
|
|
6
6
|
function parseConnectionInfoFromDSN(dsn) {
|
|
@@ -45,6 +45,7 @@ function parseConnectionInfoFromDSN(dsn) {
|
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
+
var REDACTED_DSN = "<redacted DSN>";
|
|
48
49
|
function obfuscateDSNPassword(dsn) {
|
|
49
50
|
if (!dsn) {
|
|
50
51
|
return dsn;
|
|
@@ -55,6 +56,9 @@ function obfuscateDSNPassword(dsn) {
|
|
|
55
56
|
return dsn;
|
|
56
57
|
}
|
|
57
58
|
const url = new SafeURL(dsn);
|
|
59
|
+
if (url.port && !/^\d+$/.test(url.port)) {
|
|
60
|
+
return REDACTED_DSN;
|
|
61
|
+
}
|
|
58
62
|
if (!url.password) {
|
|
59
63
|
return dsn;
|
|
60
64
|
}
|
|
@@ -79,7 +83,7 @@ function obfuscateDSNPassword(dsn) {
|
|
|
79
83
|
}
|
|
80
84
|
return result;
|
|
81
85
|
} catch {
|
|
82
|
-
return
|
|
86
|
+
return REDACTED_DSN;
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
function getDatabaseTypeFromDSN(dsn) {
|
|
@@ -97,7 +101,8 @@ function protocolToConnectorType(protocol) {
|
|
|
97
101
|
"mysql": "mysql",
|
|
98
102
|
"mariadb": "mariadb",
|
|
99
103
|
"sqlserver": "sqlserver",
|
|
100
|
-
"sqlite": "sqlite"
|
|
104
|
+
"sqlite": "sqlite",
|
|
105
|
+
"oracle": "oracle"
|
|
101
106
|
};
|
|
102
107
|
return mapping[protocol];
|
|
103
108
|
}
|
|
@@ -108,6 +113,7 @@ function getDefaultPortForType(type) {
|
|
|
108
113
|
"mysql": 3306,
|
|
109
114
|
"mariadb": 3306,
|
|
110
115
|
"sqlserver": 1433,
|
|
116
|
+
"oracle": 1521,
|
|
111
117
|
"sqlite": void 0
|
|
112
118
|
};
|
|
113
119
|
return ports[type];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
stripCommentsAndStrings
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AQKEKC24.js";
|
|
4
4
|
|
|
5
5
|
// src/utils/allowed-keywords.ts
|
|
6
6
|
var allowedKeywords = {
|
|
@@ -11,7 +11,12 @@ var allowedKeywords = {
|
|
|
11
11
|
sqlite: ["select", "with", "explain", "pragma"],
|
|
12
12
|
// SQL Server has no native EXPLAIN statement; the connector translates a
|
|
13
13
|
// leading `EXPLAIN` into a SET SHOWPLAN_XML request (see SQLServerConnector).
|
|
14
|
-
sqlserver: ["select", "with", "explain"]
|
|
14
|
+
sqlserver: ["select", "with", "explain"],
|
|
15
|
+
// Oracle's native form is `EXPLAIN PLAN FOR <stmt>`; the connector accepts
|
|
16
|
+
// both that and a bare `EXPLAIN <stmt>` and reads the plan back through
|
|
17
|
+
// DBMS_XPLAN (see OracleConnector.explainQuery). EXPLAIN PLAN only parses
|
|
18
|
+
// the statement, it never executes it.
|
|
19
|
+
oracle: ["select", "with", "explain"]
|
|
15
20
|
};
|
|
16
21
|
var mutatingKeywords = [
|
|
17
22
|
"insert",
|
|
@@ -57,12 +62,35 @@ var escapeHatchFunctionKeywords = {
|
|
|
57
62
|
mysql: ["load_file", "get_lock", "release_lock", "release_all_locks"],
|
|
58
63
|
mariadb: ["load_file", "get_lock", "release_lock", "release_all_locks"],
|
|
59
64
|
postgres: ["pg_read_file", "pg_read_binary_file", "pg_ls_dir"],
|
|
60
|
-
sqlserver: sqlServerPassThroughKeywords
|
|
65
|
+
sqlserver: sqlServerPassThroughKeywords,
|
|
66
|
+
// Oracle packages a SELECT can call to reach outside the database, matched
|
|
67
|
+
// as `package.member(` (see escapeHatchCallSuffix).
|
|
68
|
+
oracle: [
|
|
69
|
+
"utl_http",
|
|
70
|
+
"utl_tcp",
|
|
71
|
+
"utl_smtp",
|
|
72
|
+
"utl_inaddr",
|
|
73
|
+
"utl_file",
|
|
74
|
+
"dbms_ldap",
|
|
75
|
+
"dbms_sql",
|
|
76
|
+
"dbms_scheduler",
|
|
77
|
+
"dbms_java",
|
|
78
|
+
"dbms_lob",
|
|
79
|
+
"dbms_xslprocessor",
|
|
80
|
+
"dbms_advisor"
|
|
81
|
+
]
|
|
61
82
|
};
|
|
83
|
+
var escapeHatchCallSuffix = {
|
|
84
|
+
oracle: "\\s*\\.\\s*[a-z_][a-z0-9_$#]*\\s*\\("
|
|
85
|
+
};
|
|
86
|
+
var DEFAULT_CALL_SUFFIX = "\\s*\\(";
|
|
62
87
|
var escapeHatchFunctionPatterns = Object.fromEntries(
|
|
63
88
|
Object.entries(escapeHatchFunctionKeywords).map(([type, keywords]) => [
|
|
64
89
|
type,
|
|
65
|
-
new RegExp(
|
|
90
|
+
new RegExp(
|
|
91
|
+
`\\b(?:${keywords.join("|")})${escapeHatchCallSuffix[type] ?? DEFAULT_CALL_SUFFIX}`,
|
|
92
|
+
"i"
|
|
93
|
+
)
|
|
66
94
|
])
|
|
67
95
|
);
|
|
68
96
|
function hasEscapeHatchFunction(strippedSQL, connectorType) {
|
|
@@ -79,7 +107,8 @@ var mutatingPatterns = {
|
|
|
79
107
|
mysql: mutatingPatternWithReplace,
|
|
80
108
|
mariadb: mutatingPatternWithReplace,
|
|
81
109
|
sqlite: mutatingPatternWithReplace,
|
|
82
|
-
sqlserver: mutatingPatternSqlServer
|
|
110
|
+
sqlserver: mutatingPatternSqlServer,
|
|
111
|
+
oracle: mutatingPattern
|
|
83
112
|
};
|
|
84
113
|
function hasMutatingKeyword(strippedSQL, connectorType) {
|
|
85
114
|
return (mutatingPatterns[connectorType] ?? mutatingPattern).test(strippedSQL);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SQLRowLimiter,
|
|
3
3
|
closeQuietly
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-CJ5QQT7D.js";
|
|
5
|
+
import "./chunk-JKYVOIJG.js";
|
|
6
6
|
import {
|
|
7
7
|
ConnectorRegistry,
|
|
8
8
|
SafeURL,
|
|
9
9
|
splitSQLStatements
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-AQKEKC24.js";
|
|
11
11
|
|
|
12
12
|
// src/connectors/dameng/index.ts
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
package/dist/index.js
CHANGED
|
@@ -18,30 +18,30 @@ import {
|
|
|
18
18
|
resolveSourceConfigs,
|
|
19
19
|
resolveTomlConfigPath,
|
|
20
20
|
resolveTransport
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-7OHPTFUJ.js";
|
|
22
22
|
import {
|
|
23
23
|
loadConnectors
|
|
24
24
|
} from "./chunk-WVVMH6FJ.js";
|
|
25
25
|
import {
|
|
26
26
|
quoteQualifiedIdentifier
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-X2EC47W4.js";
|
|
28
28
|
import {
|
|
29
29
|
getDatabaseTypeFromDSN,
|
|
30
30
|
getDefaultPortForType,
|
|
31
31
|
parseConnectionInfoFromDSN
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-GDZT5PH3.js";
|
|
33
33
|
import {
|
|
34
34
|
allowedKeywords,
|
|
35
35
|
getFirstKeyword,
|
|
36
36
|
hasEscapeHatchFunction,
|
|
37
37
|
isReadOnlySQL,
|
|
38
38
|
sqlServerDynamicSqlPattern
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-JKYVOIJG.js";
|
|
40
40
|
import {
|
|
41
41
|
ConnectorRegistry,
|
|
42
42
|
splitSQLStatements,
|
|
43
43
|
stripCommentsAndStrings
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-AQKEKC24.js";
|
|
45
45
|
|
|
46
46
|
// src/server.ts
|
|
47
47
|
import { McpServer, createMcpHandler } from "@modelcontextprotocol/server";
|
|
@@ -1915,7 +1915,7 @@ See documentation for more details on configuring database connections.
|
|
|
1915
1915
|
const sources = sourceConfigsData.sources;
|
|
1916
1916
|
console.error(`Configuration source: ${sourceConfigsData.source}`);
|
|
1917
1917
|
await connectorManager.connectWithSources(sources);
|
|
1918
|
-
const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-
|
|
1918
|
+
const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-OYHTCTSH.js");
|
|
1919
1919
|
initializeToolRegistry2({
|
|
1920
1920
|
sources: sourceConfigsData.sources,
|
|
1921
1921
|
tools: sourceConfigsData.tools
|
|
@@ -2070,12 +2070,13 @@ See documentation for more details on configuring database connections.
|
|
|
2070
2070
|
|
|
2071
2071
|
// src/index.ts
|
|
2072
2072
|
var connectorModules = [
|
|
2073
|
-
{ load: () => import("./postgres-
|
|
2074
|
-
{ load: () => import("./sqlserver-
|
|
2075
|
-
{ load: () => import("./dameng-
|
|
2076
|
-
{ load: () => import("./sqlite-
|
|
2077
|
-
{ load: () => import("./mysql-
|
|
2078
|
-
{ load: () => import("./mariadb-
|
|
2073
|
+
{ load: () => import("./postgres-KO5JB4XV.js"), name: "PostgreSQL", driver: "pg" },
|
|
2074
|
+
{ load: () => import("./sqlserver-OQFUEISM.js"), name: "SQL Server", driver: "mssql" },
|
|
2075
|
+
{ load: () => import("./dameng-DTZZQBDP.js"), name: "Dameng", driver: "dmdb" },
|
|
2076
|
+
{ load: () => import("./sqlite-OSP72VCJ.js"), name: "SQLite", driver: "node:sqlite" },
|
|
2077
|
+
{ load: () => import("./mysql-XL7JXXWL.js"), name: "MySQL", driver: "mysql2" },
|
|
2078
|
+
{ load: () => import("./mariadb-3RX7DLHI.js"), name: "MariaDB", driver: "mariadb" },
|
|
2079
|
+
{ load: () => import("./oracle-7KQQDGH7.js"), name: "Oracle", driver: "oracledb" }
|
|
2079
2080
|
];
|
|
2080
2081
|
loadConnectors(connectorModules).then(() => main()).catch((error) => {
|
|
2081
2082
|
console.error("Fatal error:", error);
|
|
@@ -5,24 +5,24 @@ import {
|
|
|
5
5
|
parseQueryResultSets,
|
|
6
6
|
requireDatabaseInDSN,
|
|
7
7
|
withReadOnlyTransaction
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PVBTULUE.js";
|
|
9
9
|
import "./chunk-FU2ZJE4E.js";
|
|
10
10
|
import {
|
|
11
11
|
quoteIdentifier
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-X2EC47W4.js";
|
|
13
13
|
import {
|
|
14
14
|
obfuscateDSNPassword
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GDZT5PH3.js";
|
|
16
16
|
import {
|
|
17
17
|
SQLRowLimiter,
|
|
18
18
|
closeQuietly
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-CJ5QQT7D.js";
|
|
20
|
+
import "./chunk-JKYVOIJG.js";
|
|
21
21
|
import {
|
|
22
22
|
ConnectorRegistry,
|
|
23
23
|
SafeURL,
|
|
24
24
|
splitSQLStatements
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-AQKEKC24.js";
|
|
26
26
|
|
|
27
27
|
// src/connectors/mariadb/index.ts
|
|
28
28
|
import * as mariadb from "mariadb";
|
|
@@ -6,24 +6,24 @@ import {
|
|
|
6
6
|
parseQueryResultSets,
|
|
7
7
|
requireDatabaseInDSN,
|
|
8
8
|
withReadOnlyTransaction
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PVBTULUE.js";
|
|
10
10
|
import "./chunk-FU2ZJE4E.js";
|
|
11
11
|
import {
|
|
12
12
|
quoteIdentifier
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-X2EC47W4.js";
|
|
14
14
|
import {
|
|
15
15
|
obfuscateDSNPassword
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-GDZT5PH3.js";
|
|
17
17
|
import {
|
|
18
18
|
SQLRowLimiter,
|
|
19
19
|
closeQuietly
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-CJ5QQT7D.js";
|
|
21
|
+
import "./chunk-JKYVOIJG.js";
|
|
22
22
|
import {
|
|
23
23
|
ConnectorRegistry,
|
|
24
24
|
SafeURL,
|
|
25
25
|
splitSQLStatements
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-AQKEKC24.js";
|
|
27
27
|
|
|
28
28
|
// src/connectors/mysql/index.ts
|
|
29
29
|
import mysql from "mysql2/promise";
|