@carllee1983/dbcli 3.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/skills/dbcli/reference.md +31 -2
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/skills/dbcli/reference.md +31 -2
- package/CHANGELOG.md +513 -0
- package/assets/reference.md +31 -2
- package/dist/cli-runtime.mjs +3390 -1922
- package/dist/cli.mjs +3 -1
- package/dist/core.d.ts +197 -428
- package/dist/core.mjs +655 -2723
- package/gemini-extension.json +1 -1
- package/package.json +3 -1
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +31 -2
- package/skills/dbcli/reference.md +31 -2
package/dist/core.mjs
CHANGED
|
@@ -1,35 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
2
|
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
function __accessProp(key) {
|
|
8
|
-
return this[key];
|
|
9
|
-
}
|
|
10
|
-
var __toESMCache_node;
|
|
11
|
-
var __toESMCache_esm;
|
|
12
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
-
var canCache = mod != null && typeof mod === "object";
|
|
14
|
-
if (canCache) {
|
|
15
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
-
var cached = cache.get(mod);
|
|
17
|
-
if (cached)
|
|
18
|
-
return cached;
|
|
19
|
-
}
|
|
20
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
-
for (let key of __getOwnPropNames(mod))
|
|
23
|
-
if (!__hasOwnProp.call(to, key))
|
|
24
|
-
__defProp(to, key, {
|
|
25
|
-
get: __accessProp.bind(mod, key),
|
|
26
|
-
enumerable: true
|
|
27
|
-
});
|
|
28
|
-
if (canCache)
|
|
29
|
-
cache.set(mod, to);
|
|
30
|
-
return to;
|
|
31
|
-
};
|
|
32
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
3
|
var __returnValue = (v) => v;
|
|
34
4
|
function __exportSetter(name, newValue) {
|
|
35
5
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -46,76 +16,6 @@ var __export = (target, all) => {
|
|
|
46
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
17
|
var __require = import.meta.require;
|
|
48
18
|
|
|
49
|
-
// node_modules/picocolors/picocolors.js
|
|
50
|
-
var require_picocolors = __commonJS((exports, module) => {
|
|
51
|
-
var p = process || {};
|
|
52
|
-
var argv = p.argv || [];
|
|
53
|
-
var env = p.env || {};
|
|
54
|
-
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
55
|
-
var formatter = (open, close, replace = open) => (input) => {
|
|
56
|
-
let string = "" + input, index = string.indexOf(close, open.length);
|
|
57
|
-
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
58
|
-
};
|
|
59
|
-
var replaceClose = (string, close, replace, index) => {
|
|
60
|
-
let result = "", cursor = 0;
|
|
61
|
-
do {
|
|
62
|
-
result += string.substring(cursor, index) + replace;
|
|
63
|
-
cursor = index + close.length;
|
|
64
|
-
index = string.indexOf(close, cursor);
|
|
65
|
-
} while (~index);
|
|
66
|
-
return result + string.substring(cursor);
|
|
67
|
-
};
|
|
68
|
-
var createColors = (enabled = isColorSupported) => {
|
|
69
|
-
let f = enabled ? formatter : () => String;
|
|
70
|
-
return {
|
|
71
|
-
isColorSupported: enabled,
|
|
72
|
-
reset: f("\x1B[0m", "\x1B[0m"),
|
|
73
|
-
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
74
|
-
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
75
|
-
italic: f("\x1B[3m", "\x1B[23m"),
|
|
76
|
-
underline: f("\x1B[4m", "\x1B[24m"),
|
|
77
|
-
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
78
|
-
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
79
|
-
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
80
|
-
black: f("\x1B[30m", "\x1B[39m"),
|
|
81
|
-
red: f("\x1B[31m", "\x1B[39m"),
|
|
82
|
-
green: f("\x1B[32m", "\x1B[39m"),
|
|
83
|
-
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
84
|
-
blue: f("\x1B[34m", "\x1B[39m"),
|
|
85
|
-
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
86
|
-
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
87
|
-
white: f("\x1B[37m", "\x1B[39m"),
|
|
88
|
-
gray: f("\x1B[90m", "\x1B[39m"),
|
|
89
|
-
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
90
|
-
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
91
|
-
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
92
|
-
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
93
|
-
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
94
|
-
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
95
|
-
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
96
|
-
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
97
|
-
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
98
|
-
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
99
|
-
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
100
|
-
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
101
|
-
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
102
|
-
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
103
|
-
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
104
|
-
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
105
|
-
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
106
|
-
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
107
|
-
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
108
|
-
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
109
|
-
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
110
|
-
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
111
|
-
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
112
|
-
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
module.exports = createColors();
|
|
116
|
-
module.exports.createColors = createColors;
|
|
117
|
-
});
|
|
118
|
-
|
|
119
19
|
// node_modules/lru-cache/dist/esm/node/index.min.js
|
|
120
20
|
import { tracingChannel as j, channel as G } from "diagnostics_channel";
|
|
121
21
|
var w, W, C, D = () => w.hasSubscribers || W.hasSubscribers, I, L, P = (u, e, t2, i) => {
|
|
@@ -1245,281 +1145,6 @@ var ENGINE_CAPABILITIES = Object.freeze({
|
|
|
1245
1145
|
shell: cap("limited", "interactive", "Dedicated Kibana Dev Tools-style REPL (method/path + JSON body, blank-line submit); read-focused.")
|
|
1246
1146
|
})
|
|
1247
1147
|
});
|
|
1248
|
-
// src/adapters/error-mapper.ts
|
|
1249
|
-
var TRANSPORT_CODES = {
|
|
1250
|
-
ECONNREFUSED: "ECONNREFUSED",
|
|
1251
|
-
ETIMEDOUT: "ETIMEDOUT",
|
|
1252
|
-
ESOCKETTIMEDOUT: "ETIMEDOUT",
|
|
1253
|
-
ENOTFOUND: "ENOTFOUND",
|
|
1254
|
-
EAI_AGAIN: "ENOTFOUND",
|
|
1255
|
-
PROTOCOL_CONNECTION_LOST: "CONNECTION_LOST",
|
|
1256
|
-
PROTOCOL_SEQUENCE_TIMEOUT: "ETIMEDOUT",
|
|
1257
|
-
ECONNRESET: "CONNECTION_LOST",
|
|
1258
|
-
EPIPE: "CONNECTION_LOST",
|
|
1259
|
-
ECONNABORTED: "CONNECTION_LOST",
|
|
1260
|
-
EHOSTUNREACH: "EHOSTUNREACH",
|
|
1261
|
-
ENETUNREACH: "EHOSTUNREACH",
|
|
1262
|
-
ENETDOWN: "EHOSTUNREACH"
|
|
1263
|
-
};
|
|
1264
|
-
var TLS_CODE_PREFIX = /^(CERT_|SELF_SIGNED_|DEPTH_ZERO_|UNABLE_TO_(?:GET|VERIFY)_|ERR_TLS_|ERR_SSL_)/;
|
|
1265
|
-
var MYSQL_CODES = {
|
|
1266
|
-
ER_NO_SUCH_TABLE: "TABLE_NOT_FOUND",
|
|
1267
|
-
ER_BAD_FIELD_ERROR: "COLUMN_NOT_FOUND",
|
|
1268
|
-
ER_PARSE_ERROR: "SQL_SYNTAX_ERROR",
|
|
1269
|
-
ER_ACCESS_DENIED_ERROR: "AUTH_FAILED",
|
|
1270
|
-
ER_DBACCESS_DENIED_ERROR: "AUTH_FAILED",
|
|
1271
|
-
ER_NOT_SUPPORTED_AUTH_MODE: "AUTH_FAILED",
|
|
1272
|
-
ER_MUST_CHANGE_PASSWORD_LOGIN: "AUTH_FAILED",
|
|
1273
|
-
ER_QUERY_TIMEOUT: "STATEMENT_TIMEOUT",
|
|
1274
|
-
ER_STATEMENT_TIMEOUT: "STATEMENT_TIMEOUT",
|
|
1275
|
-
ER_CON_COUNT_ERROR: "TOO_MANY_CONNECTIONS",
|
|
1276
|
-
ER_SERVER_SHUTDOWN: "CONNECTION_LOST"
|
|
1277
|
-
};
|
|
1278
|
-
var MYSQL_ERRNOS = {
|
|
1279
|
-
1045: "AUTH_FAILED",
|
|
1280
|
-
1044: "AUTH_FAILED",
|
|
1281
|
-
1054: "COLUMN_NOT_FOUND",
|
|
1282
|
-
1064: "SQL_SYNTAX_ERROR",
|
|
1283
|
-
1146: "TABLE_NOT_FOUND",
|
|
1284
|
-
1251: "AUTH_FAILED",
|
|
1285
|
-
1698: "AUTH_FAILED",
|
|
1286
|
-
1040: "TOO_MANY_CONNECTIONS",
|
|
1287
|
-
1053: "CONNECTION_LOST",
|
|
1288
|
-
2006: "CONNECTION_LOST",
|
|
1289
|
-
2013: "CONNECTION_LOST",
|
|
1290
|
-
1969: "STATEMENT_TIMEOUT",
|
|
1291
|
-
3024: "STATEMENT_TIMEOUT"
|
|
1292
|
-
};
|
|
1293
|
-
var SQLSTATES = {
|
|
1294
|
-
"28000": "AUTH_FAILED",
|
|
1295
|
-
"28P01": "AUTH_FAILED",
|
|
1296
|
-
"42601": "SQL_SYNTAX_ERROR",
|
|
1297
|
-
"42P01": "TABLE_NOT_FOUND",
|
|
1298
|
-
"42703": "COLUMN_NOT_FOUND",
|
|
1299
|
-
"57014": "STATEMENT_TIMEOUT",
|
|
1300
|
-
"08000": "CONNECTION_LOST",
|
|
1301
|
-
"08003": "CONNECTION_LOST",
|
|
1302
|
-
"08006": "CONNECTION_LOST",
|
|
1303
|
-
"08001": "ECONNREFUSED",
|
|
1304
|
-
"08004": "CONNECTION_REJECTED",
|
|
1305
|
-
"53300": "TOO_MANY_CONNECTIONS",
|
|
1306
|
-
"57P01": "CONNECTION_LOST",
|
|
1307
|
-
"57P02": "CONNECTION_LOST",
|
|
1308
|
-
"57P03": "SERVER_NOT_READY"
|
|
1309
|
-
};
|
|
1310
|
-
var REDIS_PREFIXES = {
|
|
1311
|
-
NOAUTH: "AUTH_FAILED",
|
|
1312
|
-
WRONGPASS: "AUTH_FAILED"
|
|
1313
|
-
};
|
|
1314
|
-
var FALLBACK_PATTERNS = [
|
|
1315
|
-
[/connection refused/i, "ECONNREFUSED"],
|
|
1316
|
-
[/\bECONNREFUSED\b/, "ECONNREFUSED"],
|
|
1317
|
-
[/connect(?:ion)? time(?:d )?out/i, "ETIMEDOUT"],
|
|
1318
|
-
[/timeout exceeded/i, "ETIMEDOUT"],
|
|
1319
|
-
[/timed out/i, "ETIMEDOUT"],
|
|
1320
|
-
[/connection terminated/i, "CONNECTION_LOST"],
|
|
1321
|
-
[/server closed the connection/i, "CONNECTION_LOST"],
|
|
1322
|
-
[/server has gone away/i, "CONNECTION_LOST"],
|
|
1323
|
-
[/lost connection to \S+ server/i, "CONNECTION_LOST"],
|
|
1324
|
-
[/getaddrinfo/i, "ENOTFOUND"],
|
|
1325
|
-
[/authentication failed/i, "AUTH_FAILED"],
|
|
1326
|
-
[/access denied for user/i, "AUTH_FAILED"],
|
|
1327
|
-
[/no pg_hba\.conf entry/i, "AUTH_FAILED"],
|
|
1328
|
-
[/role\s+".*?"\s+does not exist/i, "AUTH_FAILED"],
|
|
1329
|
-
[/password supplied/i, "AUTH_FAILED"]
|
|
1330
|
-
];
|
|
1331
|
-
var PG_CANCEL_BY_TIMEOUT = /statement timeout/i;
|
|
1332
|
-
function categorize(errCode, errno, errMsg, system) {
|
|
1333
|
-
if (errCode) {
|
|
1334
|
-
if (TLS_CODE_PREFIX.test(errCode))
|
|
1335
|
-
return "TLS_ERROR";
|
|
1336
|
-
const known = TRANSPORT_CODES[errCode] ?? MYSQL_CODES[errCode] ?? SQLSTATES[errCode] ?? REDIS_PREFIXES[errCode];
|
|
1337
|
-
if (known === "STATEMENT_TIMEOUT" && errCode === "57014" && !PG_CANCEL_BY_TIMEOUT.test(errMsg)) {
|
|
1338
|
-
return null;
|
|
1339
|
-
}
|
|
1340
|
-
if (known)
|
|
1341
|
-
return known;
|
|
1342
|
-
}
|
|
1343
|
-
const isMysqlFamily = system === "mysql" || system === "mariadb";
|
|
1344
|
-
if (isMysqlFamily && typeof errno === "number" && MYSQL_ERRNOS[errno]) {
|
|
1345
|
-
return MYSQL_ERRNOS[errno];
|
|
1346
|
-
}
|
|
1347
|
-
const redisPrefix = errMsg.match(/^([A-Z]+)\s/)?.[1];
|
|
1348
|
-
if (redisPrefix && REDIS_PREFIXES[redisPrefix])
|
|
1349
|
-
return REDIS_PREFIXES[redisPrefix];
|
|
1350
|
-
if (errCode || isMysqlFamily && typeof errno === "number")
|
|
1351
|
-
return null;
|
|
1352
|
-
for (const [pattern, category] of FALLBACK_PATTERNS) {
|
|
1353
|
-
if (pattern.test(errMsg))
|
|
1354
|
-
return category;
|
|
1355
|
-
}
|
|
1356
|
-
return null;
|
|
1357
|
-
}
|
|
1358
|
-
var SYSTEM_FACTS = {
|
|
1359
|
-
postgresql: {
|
|
1360
|
-
serviceCheck: "systemctl status postgresql",
|
|
1361
|
-
defaultPort: "default 5432",
|
|
1362
|
-
firewallRule: "allow TCP 5432",
|
|
1363
|
-
client: "psql",
|
|
1364
|
-
logFile: "postgresql.log"
|
|
1365
|
-
},
|
|
1366
|
-
mysql: {
|
|
1367
|
-
serviceCheck: "systemctl status mysql",
|
|
1368
|
-
defaultPort: "default 3306",
|
|
1369
|
-
firewallRule: "allow TCP 3306",
|
|
1370
|
-
client: "mysql",
|
|
1371
|
-
logFile: "mysql.log"
|
|
1372
|
-
},
|
|
1373
|
-
mariadb: {
|
|
1374
|
-
serviceCheck: "systemctl status mariadb",
|
|
1375
|
-
defaultPort: "default 3306",
|
|
1376
|
-
firewallRule: "allow TCP 3306",
|
|
1377
|
-
client: "mariadb",
|
|
1378
|
-
logFile: "mariadb.log"
|
|
1379
|
-
},
|
|
1380
|
-
redis: {
|
|
1381
|
-
serviceCheck: "redis-cli ping",
|
|
1382
|
-
defaultPort: "default 6379",
|
|
1383
|
-
firewallRule: "allow TCP 6379",
|
|
1384
|
-
client: "redis-cli",
|
|
1385
|
-
logFile: "redis.log"
|
|
1386
|
-
}
|
|
1387
|
-
};
|
|
1388
|
-
function serviceHints(system, options) {
|
|
1389
|
-
const facts = SYSTEM_FACTS[system];
|
|
1390
|
-
return [
|
|
1391
|
-
`Check that the ${system} service is running: ${facts.serviceCheck}`,
|
|
1392
|
-
`Verify the port is correct: ${facts.defaultPort}`,
|
|
1393
|
-
`Check that ${options.host} is reachable: ping ${options.host} or telnet ${options.host} ${options.port}`
|
|
1394
|
-
];
|
|
1395
|
-
}
|
|
1396
|
-
function mapError(error, system, options) {
|
|
1397
|
-
if (error instanceof ConnectionError)
|
|
1398
|
-
return error;
|
|
1399
|
-
const err = error;
|
|
1400
|
-
const errMsg = String(err?.message || String(error));
|
|
1401
|
-
const errCode = String(err?.code || "");
|
|
1402
|
-
const errno = err?.errno;
|
|
1403
|
-
switch (categorize(errCode, errno, errMsg, system)) {
|
|
1404
|
-
case "ECONNREFUSED":
|
|
1405
|
-
return new ConnectionError("ECONNREFUSED", `Cannot connect to ${options.host}:${options.port} \u2014 server is not running or not listening on this port`, serviceHints(system, options));
|
|
1406
|
-
case "ETIMEDOUT":
|
|
1407
|
-
return new ConnectionError("ETIMEDOUT", `Connection timed out (${options.timeout || 5000}ms) \u2014 may be blocked by a firewall or slow network`, [
|
|
1408
|
-
`Check firewall rules: ${SYSTEM_FACTS[system].firewallRule}`,
|
|
1409
|
-
`Increase timeout: edit .dbcli and add "timeout": 15000`,
|
|
1410
|
-
`Verify network connectivity: ping ${options.host} -c 3`
|
|
1411
|
-
]);
|
|
1412
|
-
case "CONNECTION_LOST":
|
|
1413
|
-
return new ConnectionError("CONNECTION_LOST", `Connection to ${options.host}:${options.port} was lost mid-session \u2014 the server closed it or the network dropped`, [
|
|
1414
|
-
"Re-run the command: a dropped connection is often transient",
|
|
1415
|
-
`Check whether the server restarted or is cycling: ${SYSTEM_FACTS[system].logFile}`,
|
|
1416
|
-
"If it happens on long-running work, look at the server idle/wait timeout"
|
|
1417
|
-
]);
|
|
1418
|
-
case "SERVER_NOT_READY":
|
|
1419
|
-
return new ConnectionError("SERVER_NOT_READY", `${options.host}:${options.port} is not accepting connections yet \u2014 the server is starting up or recovering`, [
|
|
1420
|
-
"Retry shortly: this clears on its own once startup or recovery finishes",
|
|
1421
|
-
`Watch progress in the server log: ${SYSTEM_FACTS[system].logFile}`,
|
|
1422
|
-
"On a replica, this also appears while it catches up with the primary"
|
|
1423
|
-
]);
|
|
1424
|
-
case "CONNECTION_REJECTED":
|
|
1425
|
-
return new ConnectionError("CONNECTION_REJECTED", `${options.host}:${options.port} answered but rejected the connection attempt`, [
|
|
1426
|
-
"Check the server access rules (pg_hba.conf) for this user, database, and client address",
|
|
1427
|
-
"If a pooler (pgbouncer / ProxySQL) sits in front, check its own rules and limits",
|
|
1428
|
-
"Check whether the connection limit for this user or database is exhausted"
|
|
1429
|
-
]);
|
|
1430
|
-
case "TOO_MANY_CONNECTIONS":
|
|
1431
|
-
return new ConnectionError("TOO_MANY_CONNECTIONS", `${options.host}:${options.port} refused the connection \u2014 the server has no connection slots left`, [
|
|
1432
|
-
"Wait and retry: the limit is on concurrent connections, not on you",
|
|
1433
|
-
system === "postgresql" ? "Inspect usage: SELECT count(*) FROM pg_stat_activity \u2014 compare against max_connections" : 'Inspect usage: SHOW STATUS LIKE "Threads_connected" \u2014 compare against max_connections',
|
|
1434
|
-
"Close idle sessions, or raise max_connections if the load is legitimate"
|
|
1435
|
-
]);
|
|
1436
|
-
case "EHOSTUNREACH":
|
|
1437
|
-
return new ConnectionError("EHOSTUNREACH", `No route to ${options.host} \u2014 the name resolved, but the host or network is unreachable`, [
|
|
1438
|
-
"Check that you are on the right network or VPN",
|
|
1439
|
-
`Check routing and firewall between here and ${options.host}`,
|
|
1440
|
-
`Confirm the address is the one you meant: ${options.host}:${options.port}`
|
|
1441
|
-
]);
|
|
1442
|
-
case "TLS_ERROR":
|
|
1443
|
-
return new ConnectionError("TLS_ERROR", `TLS handshake failed: ${errMsg}`, [
|
|
1444
|
-
'Point the connection at the right CA bundle: set "caPath" in .dbcli',
|
|
1445
|
-
'For a self-signed certificate in a trusted network, set "rejectUnauthorized": false',
|
|
1446
|
-
`Confirm the certificate covers the host you connected to: ${options.host}`
|
|
1447
|
-
]);
|
|
1448
|
-
case "ENOTFOUND":
|
|
1449
|
-
return new ConnectionError("ENOTFOUND", `Host not found: ${options.host}`, [
|
|
1450
|
-
`Check the hostname spelling: ${options.host}`,
|
|
1451
|
-
`Verify DNS resolution: nslookup ${options.host}`,
|
|
1452
|
-
`If using localhost, try 127.0.0.1 (IPv4 vs IPv6 issue)`
|
|
1453
|
-
]);
|
|
1454
|
-
case "AUTH_FAILED":
|
|
1455
|
-
return new ConnectionError("AUTH_FAILED", `Authentication failed \u2014 check your username or password`, [
|
|
1456
|
-
`Verify credentials: ${SYSTEM_FACTS[system].client} ${system === "postgresql" ? "-U" : "-u"} ${options.user} -h ${options.host}`,
|
|
1457
|
-
`Check pg_hba.conf (PostgreSQL) or user privileges (MySQL)`,
|
|
1458
|
-
`Re-run dbcli init to update credentials`
|
|
1459
|
-
]);
|
|
1460
|
-
case "TABLE_NOT_FOUND": {
|
|
1461
|
-
const mysqlMatch = errMsg.match(/Table\s+'(?:[^.']+\.)?([^']+)'/i);
|
|
1462
|
-
const pgMatch = errMsg.match(/relation\s+"([^"]+)"/i);
|
|
1463
|
-
const tableName = mysqlMatch?.[1] || pgMatch?.[1] || "unknown";
|
|
1464
|
-
return new ConnectionError("TABLE_NOT_FOUND", `Table '${tableName}' not found in database '${options.database || "unknown"}'`, [
|
|
1465
|
-
"Run `dbcli list` to see available tables",
|
|
1466
|
-
"Run `dbcli schema <table>` to confirm the exact name (ORM export names may differ from DB table names)"
|
|
1467
|
-
]);
|
|
1468
|
-
}
|
|
1469
|
-
case "COLUMN_NOT_FOUND": {
|
|
1470
|
-
const mysqlMatch = errMsg.match(/Unknown column\s+'([^']+)'/i);
|
|
1471
|
-
const pgMatch = errMsg.match(/column\s+"([^"]+)"\s+does not exist/i);
|
|
1472
|
-
const columnName = mysqlMatch?.[1] || pgMatch?.[1] || "unknown";
|
|
1473
|
-
return new ConnectionError("COLUMN_NOT_FOUND", `Column '${columnName}' not found`, [
|
|
1474
|
-
"Run `dbcli schema <table>` to see the actual column names",
|
|
1475
|
-
"Column names are case-sensitive on some databases (PostgreSQL with quoted identifiers)"
|
|
1476
|
-
]);
|
|
1477
|
-
}
|
|
1478
|
-
case "STATEMENT_TIMEOUT": {
|
|
1479
|
-
const limitMs = options.statementTimeout ?? options.timeout;
|
|
1480
|
-
return new ConnectionError("STATEMENT_TIMEOUT", `Statement timed out${limitMs ? ` (${limitMs}ms)` : ""} \u2014 the server canceled this query before it finished`, [
|
|
1481
|
-
'Inspect the query plan: dbcli explain "<sql>"',
|
|
1482
|
-
"Raise the ceiling for this run: dbcli --statement-timeout <ms> \u2026 (0 removes it)",
|
|
1483
|
-
"Narrow the query: add WHERE filters, reduce the LIMIT, or index the scanned columns"
|
|
1484
|
-
], limitMs);
|
|
1485
|
-
}
|
|
1486
|
-
case "SQL_SYNTAX_ERROR":
|
|
1487
|
-
return new ConnectionError("SQL_SYNTAX_ERROR", `SQL syntax error: ${errMsg}`, [
|
|
1488
|
-
"Check your SQL syntax near the position reported above",
|
|
1489
|
-
"In query-only mode, dbcli auto-appends LIMIT 1000; use --no-limit to disable",
|
|
1490
|
-
"For SHOW/DESCRIBE/EXPLAIN statements, LIMIT is not allowed \u2014 these are not auto-limited"
|
|
1491
|
-
]);
|
|
1492
|
-
}
|
|
1493
|
-
const reportedCode = errCode || (typeof errno === "number" ? String(errno) : "");
|
|
1494
|
-
if (reportedCode) {
|
|
1495
|
-
return new ConnectionError("UNKNOWN", `Database error (${reportedCode}): ${errMsg}`, [
|
|
1496
|
-
"Run `dbcli schema <table>` to confirm the objects referenced by this statement",
|
|
1497
|
-
`Look up ${reportedCode} in the ${system} error reference for the exact cause`
|
|
1498
|
-
]);
|
|
1499
|
-
}
|
|
1500
|
-
return new ConnectionError("UNKNOWN", `Connection failed: ${errMsg}`, [
|
|
1501
|
-
`Check connection parameters: host=${options.host}, port=${options.port}, user=${options.user}`,
|
|
1502
|
-
`View server logs: ${SYSTEM_FACTS[system].logFile}`,
|
|
1503
|
-
`Try connecting directly with the ${SYSTEM_FACTS[system].client} command-line tool`
|
|
1504
|
-
]);
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// src/adapters/sql-adapter-utils.ts
|
|
1508
|
-
function requireConnected(connection) {
|
|
1509
|
-
if (!connection) {
|
|
1510
|
-
throw new ConnectionError("UNKNOWN", "Database connection not established", [
|
|
1511
|
-
"Call connect() to establish a connection"
|
|
1512
|
-
]);
|
|
1513
|
-
}
|
|
1514
|
-
return connection;
|
|
1515
|
-
}
|
|
1516
|
-
async function withMappedConnectionError(system, options, operation) {
|
|
1517
|
-
try {
|
|
1518
|
-
return await operation();
|
|
1519
|
-
} catch (error) {
|
|
1520
|
-
throw mapError(error, system, options);
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
1148
|
// resources/lang/en/messages.json
|
|
1524
1149
|
var messages_default = {
|
|
1525
1150
|
init: {
|
|
@@ -1782,7 +1407,8 @@ Reference: {referencePath}`,
|
|
|
1782
1407
|
table_added: "\u2713 Table '{table}' added to blacklist",
|
|
1783
1408
|
table_removed: "\u2713 Table '{table}' removed from blacklist",
|
|
1784
1409
|
column_added: "\u2713 Column '{table}.{column}' added to blacklist",
|
|
1785
|
-
column_removed: "\u2713 Column '{table}.{column}' removed from blacklist"
|
|
1410
|
+
column_removed: "\u2713 Column '{table}.{column}' removed from blacklist",
|
|
1411
|
+
refuse_wildcard: "Refused: '{table}' contains wildcard characters, which are matched literally on {system} \u2014 the entry would never match anything. Write the exact name."
|
|
1786
1412
|
},
|
|
1787
1413
|
security: {
|
|
1788
1414
|
columns_omitted: "Security: {count} column(s) were omitted based on your blacklist"
|
|
@@ -2095,7 +1721,8 @@ var messages_default2 = {
|
|
|
2095
1721
|
table_added: "\u2713 \u8868\u683C '{table}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
|
|
2096
1722
|
table_removed: "\u2713 \u8868\u683C '{table}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
|
|
2097
1723
|
column_added: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
|
|
2098
|
-
column_removed: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664"
|
|
1724
|
+
column_removed: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
|
|
1725
|
+
refuse_wildcard: "\u5DF2\u62D2\u7D55\uFF1A'{table}' \u542B\u6709\u842C\u7528\u5B57\u5143\uFF0C\u800C {system} \u4E0A\u7684\u6BD4\u5C0D\u662F\u9010\u5B57\u9032\u884C\u7684\u2014\u2014\u9019\u7B46\u8A2D\u5B9A\u6C38\u9060\u4E0D\u6703\u547D\u4E2D\u4EFB\u4F55\u6771\u897F\u3002\u8ACB\u5BEB\u51FA\u78BA\u5207\u7684\u540D\u7A31\u3002"
|
|
2099
1726
|
},
|
|
2100
1727
|
security: {
|
|
2101
1728
|
columns_omitted: "\u5B89\u5168: {count} \u500B\u6B04\u4F4D\u6839\u64DA\u4F60\u7684\u9ED1\u540D\u55AE\u5DF2\u88AB\u96B1\u85CF"
|
|
@@ -2167,7 +1794,27 @@ var shell_default = {
|
|
|
2167
1794
|
timing_display: "Time: {ms}ms",
|
|
2168
1795
|
rows_display: "{count} row(s)",
|
|
2169
1796
|
unknown_command: "Unknown command: '{command}'. Type '.help' for available commands.",
|
|
2170
|
-
subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels."
|
|
1797
|
+
subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels.",
|
|
1798
|
+
es: {
|
|
1799
|
+
parse_empty: "Empty request",
|
|
1800
|
+
parse_needs_method_path: 'Request requires a method and a path, e.g. "GET /index/_search"',
|
|
1801
|
+
parse_needs_path: "Request requires a path",
|
|
1802
|
+
refuse_dot_segment: "Refused: '{path}' contains a '..' segment once decoded, and dbcli cannot tell which path Elasticsearch would route it to. Write the path out without '..'.",
|
|
1803
|
+
refuse_unparseable: "Refused: '{path}' is not the request the server would receive. It cannot be parsed as a path.",
|
|
1804
|
+
refuse_not_canonical: "Refused: '{path}' is not the request the server would receive. Write it as '{canonical}'.",
|
|
1805
|
+
refuse_source_param: "Refused: `source` in the query string is a request body by another route. Write the body as JSON.",
|
|
1806
|
+
refuse_search_template: "Refused: '{path}' is a search template, whose body cannot be inspected \u2014 it is a string rendered on the cluster, or stored there. Write the query out in full.",
|
|
1807
|
+
refuse_string_body: "Refused: a quoted string request body is not supported. Write the body as JSON.",
|
|
1808
|
+
refuse_audit_strict: "Refused: the audit entry for this request could not be written, and audit.strict is on. Fix the audit sink (disk space, directory permissions, a stale lockfile in .dbcli/audit) or turn audit.strict off to accept unrecorded requests.",
|
|
1809
|
+
blacklist_index: "index '{index}' is blacklist-protected",
|
|
1810
|
+
blacklist_unscoped: "'{path}' names no index, so it cannot be checked against the blacklist. Scope the request to an index, e.g. GET /<index>/_search.",
|
|
1811
|
+
blacklist_field: "field '{field}' is blacklist-protected and cannot be named in a request \u2014 sorting, aggregating or scripting on it would return its values.",
|
|
1812
|
+
banner_title: "Elasticsearch shell \u2014 Kibana Dev Tools syntax",
|
|
1813
|
+
banner_hint: 'Enter "<METHOD> /<path>" then an optional JSON body; submit with a blank line. Try: GET /_cat/indices',
|
|
1814
|
+
banner_keys: 'Ctrl+C cancels the current block; Ctrl+D or "exit" quits.',
|
|
1815
|
+
block_cancelled: "(block cancelled)",
|
|
1816
|
+
goodbye: "Goodbye"
|
|
1817
|
+
}
|
|
2171
1818
|
};
|
|
2172
1819
|
// resources/lang/zh-TW/shell.json
|
|
2173
1820
|
var shell_default2 = {
|
|
@@ -2190,7 +1837,27 @@ var shell_default2 = {
|
|
|
2190
1837
|
timing_display: "\u8017\u6642\uFF1A{ms}ms",
|
|
2191
1838
|
rows_display: "{count} \u7B46\u8CC7\u6599",
|
|
2192
1839
|
unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002",
|
|
2193
|
-
subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002"
|
|
1840
|
+
subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002",
|
|
1841
|
+
es: {
|
|
1842
|
+
parse_empty: "\u7A7A\u7684\u8ACB\u6C42",
|
|
1843
|
+
parse_needs_method_path: '\u8ACB\u6C42\u9700\u8981\u4E00\u500B method \u8207\u4E00\u500B path\uFF0C\u4F8B\u5982 "GET /index/_search"',
|
|
1844
|
+
parse_needs_path: "\u8ACB\u6C42\u9700\u8981\u4E00\u500B path",
|
|
1845
|
+
refuse_dot_segment: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u89E3\u78BC\u5F8C\u542B\u6709 '..' \u5340\u6BB5\uFF0Cdbcli \u7121\u6CD5\u5224\u65B7 Elasticsearch \u6703\u628A\u5B83\u8DEF\u7531\u5230\u54EA\u500B\u8DEF\u5F91\u3002\u8ACB\u628A\u8DEF\u5F91\u5BEB\u6210\u4E0D\u542B '..' \u7684\u5F62\u5F0F\u3002",
|
|
1846
|
+
refuse_unparseable: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u5B83\u7121\u6CD5\u88AB\u89E3\u6790\u6210\u4E00\u500B\u8DEF\u5F91\u3002",
|
|
1847
|
+
refuse_not_canonical: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u8ACB\u5BEB\u6210 '{canonical}'\u3002",
|
|
1848
|
+
refuse_source_param: "\u5DF2\u62D2\u7D55\uFF1Aquery string \u88E1\u7684 `source` \u662F\u53E6\u4E00\u689D\u8DEF\u5F91\u4E0A\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
|
|
1849
|
+
refuse_search_template: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u662F search template\uFF0C\u5B83\u7684 body \u7121\u6CD5\u6AA2\u67E5\u2014\u2014\u90A3\u662F\u4E00\u500B\u5728\u53E2\u96C6\u4E0A\u7B97\u51FA\u4F86\u7684\u5B57\u4E32\uFF0C\u6216\u8005\u6839\u672C\u5B58\u5728\u53E2\u96C6\u88E1\u3002\u8ACB\u628A\u67E5\u8A62\u5B8C\u6574\u5BEB\u51FA\u4F86\u3002",
|
|
1850
|
+
refuse_string_body: "\u5DF2\u62D2\u7D55\uFF1A\u4E0D\u652F\u63F4\u4EE5\u5F15\u865F\u5B57\u4E32\u5F62\u5F0F\u5BEB\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
|
|
1851
|
+
refuse_audit_strict: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u500B\u8ACB\u6C42\u7684\u7A3D\u6838\u7D00\u9304\u5BEB\u4E0D\u51FA\u53BB\uFF0C\u800C audit.strict \u662F\u958B\u7684\u3002\u8ACB\u4FEE\u597D\u7A3D\u6838\u8F38\u51FA\u7AEF\uFF08\u78C1\u789F\u7A7A\u9593\u3001\u76EE\u9304\u6B0A\u9650\u3001.dbcli/audit \u88E1\u6B98\u7559\u7684 lockfile\uFF09\uFF0C\u6216\u95DC\u6389 audit.strict \u4EE5\u63A5\u53D7\u6C92\u6709\u7D00\u9304\u7684\u8ACB\u6C42\u3002",
|
|
1852
|
+
blacklist_index: "\u7D22\u5F15 '{index}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77",
|
|
1853
|
+
blacklist_unscoped: "'{path}' \u6307\u4E0D\u51FA\u4EFB\u4F55\u7D22\u5F15\uFF0C\u56E0\u6B64\u7121\u6CD5\u5C0D\u7167\u9ED1\u540D\u55AE\u6AA2\u67E5\u3002\u8ACB\u628A\u8ACB\u6C42\u9650\u5B9A\u5230\u67D0\u500B\u7D22\u5F15\uFF0C\u4F8B\u5982 GET /<index>/_search\u3002",
|
|
1854
|
+
blacklist_field: "\u6B04\u4F4D '{field}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77\uFF0C\u4E0D\u80FD\u51FA\u73FE\u5728\u8ACB\u6C42\u88E1\u2014\u2014\u5C0D\u5B83\u6392\u5E8F\u3001\u805A\u5408\u6216\u8DD1 script \u90FD\u6703\u628A\u5B83\u7684\u503C\u53D6\u56DE\u4F86\u3002",
|
|
1855
|
+
banner_title: "Elasticsearch shell \u2014\u2014 Kibana Dev Tools \u8A9E\u6CD5",
|
|
1856
|
+
banner_hint: '\u8F38\u5165 "<METHOD> /<path>"\uFF0C\u63A5\u8457\u5BEB\u53EF\u9078\u7684 JSON body\uFF1B\u7A7A\u4E00\u884C\u9001\u51FA\u3002\u53EF\u4EE5\u5148\u8A66\uFF1AGET /_cat/indices',
|
|
1857
|
+
banner_keys: 'Ctrl+C \u53D6\u6D88\u76EE\u524D\u9019\u500B block\uFF1BCtrl+D \u6216 "exit" \u96E2\u958B\u3002',
|
|
1858
|
+
block_cancelled: "\uFF08block \u5DF2\u53D6\u6D88\uFF09",
|
|
1859
|
+
goodbye: "\u518D\u898B"
|
|
1860
|
+
}
|
|
2194
1861
|
};
|
|
2195
1862
|
// resources/lang/en/ceremony.json
|
|
2196
1863
|
var ceremony_default = {
|
|
@@ -2344,2585 +2011,686 @@ class MessageLoader {
|
|
|
2344
2011
|
}
|
|
2345
2012
|
return key;
|
|
2346
2013
|
}
|
|
2014
|
+
setLanguage(lang) {
|
|
2015
|
+
this.currentLang = lang;
|
|
2016
|
+
this.messages = {};
|
|
2017
|
+
this.loadMessages();
|
|
2018
|
+
}
|
|
2347
2019
|
interpolate(key, vars) {
|
|
2348
|
-
|
|
2349
|
-
for (const [varName, value] of Object.entries(vars)) {
|
|
2350
|
-
const escapedVarName = varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2351
|
-
const regex = new RegExp(`{${escapedVarName}}`, "g");
|
|
2352
|
-
message = message.replace(regex, String(value));
|
|
2353
|
-
}
|
|
2354
|
-
return message;
|
|
2020
|
+
return this.t(key).replace(/\{([^{}]+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : match);
|
|
2355
2021
|
}
|
|
2356
2022
|
}
|
|
2357
2023
|
var messageLoader = MessageLoader.getInstance();
|
|
2358
2024
|
var t = (key) => messageLoader.t(key);
|
|
2359
2025
|
var t_vars = (key, vars) => messageLoader.interpolate(key, vars);
|
|
2360
2026
|
|
|
2361
|
-
// src/utils/
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
info: (text) => import_picocolors.default.blue(text),
|
|
2368
|
-
dim: (text) => import_picocolors.default.dim(text),
|
|
2369
|
-
bold: (text) => import_picocolors.default.bold(text),
|
|
2370
|
-
keyword: (text) => import_picocolors.default.blue(import_picocolors.default.bold(text))
|
|
2371
|
-
};
|
|
2372
|
-
|
|
2373
|
-
// src/utils/db-version-check.ts
|
|
2374
|
-
var MIN_SUPPORTED_VERSIONS = {
|
|
2375
|
-
postgresql: "12.0",
|
|
2376
|
-
mysql: "8.0",
|
|
2377
|
-
mariadb: "10.5",
|
|
2378
|
-
elasticsearch: "7.10"
|
|
2379
|
-
};
|
|
2380
|
-
function parseVersionSegments(version) {
|
|
2381
|
-
const match = version.match(/^(\d+(?:\.\d+)*)/);
|
|
2382
|
-
if (!match)
|
|
2383
|
-
return [];
|
|
2384
|
-
return (match[1] ?? "").split(".").map(Number);
|
|
2385
|
-
}
|
|
2386
|
-
function isMariaDBVersion(versionString) {
|
|
2387
|
-
return /mariadb/i.test(versionString);
|
|
2388
|
-
}
|
|
2389
|
-
function extractMariaDBVersion(versionString) {
|
|
2390
|
-
const match = versionString.match(/(\d+\.\d+\.\d+)-MariaDB/i);
|
|
2391
|
-
if (match)
|
|
2392
|
-
return match[1] ?? versionString;
|
|
2393
|
-
const prefixMatch = versionString.match(/^5\.5\.5-(\d+\.\d+\.\d+)/);
|
|
2394
|
-
if (prefixMatch)
|
|
2395
|
-
return prefixMatch[1] ?? versionString;
|
|
2396
|
-
return versionString;
|
|
2397
|
-
}
|
|
2398
|
-
function compareVersions(a, b) {
|
|
2399
|
-
const segA = parseVersionSegments(a);
|
|
2400
|
-
const segB = parseVersionSegments(b);
|
|
2401
|
-
const len = Math.max(segA.length, segB.length);
|
|
2402
|
-
for (let i = 0;i < len; i++) {
|
|
2403
|
-
const diff = (segA[i] ?? 0) - (segB[i] ?? 0);
|
|
2404
|
-
if (diff !== 0)
|
|
2405
|
-
return diff;
|
|
2406
|
-
}
|
|
2407
|
-
return 0;
|
|
2408
|
-
}
|
|
2409
|
-
function checkDbVersion(rawVersion, declaredSystem) {
|
|
2410
|
-
const isMariaDB = isMariaDBVersion(rawVersion);
|
|
2411
|
-
const system = isMariaDB ? "mariadb" : declaredSystem;
|
|
2412
|
-
const serverVersion = isMariaDB ? extractMariaDBVersion(rawVersion) : rawVersion;
|
|
2413
|
-
const minVersion = MIN_SUPPORTED_VERSIONS[system];
|
|
2414
|
-
const supported = compareVersions(serverVersion, minVersion ?? "0") >= 0;
|
|
2415
|
-
return {
|
|
2416
|
-
serverVersion,
|
|
2417
|
-
system,
|
|
2418
|
-
supported,
|
|
2419
|
-
minVersion: minVersion ?? "0"
|
|
2420
|
-
};
|
|
2421
|
-
}
|
|
2422
|
-
function warnIfUnsupported(result) {
|
|
2423
|
-
if (result.supported)
|
|
2424
|
-
return;
|
|
2425
|
-
const message = t_vars("version.unsupported_warning", {
|
|
2426
|
-
system: result.system,
|
|
2427
|
-
version: result.serverVersion,
|
|
2428
|
-
minVersion: result.minVersion
|
|
2429
|
-
});
|
|
2430
|
-
process.stderr.write(colors.warn(`\u26A0 ${message}`) + `
|
|
2431
|
-
`);
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
|
-
// src/utils/encoding.ts
|
|
2435
|
-
var CP1252_REVERSE = {
|
|
2436
|
-
8364: 128,
|
|
2437
|
-
8218: 130,
|
|
2438
|
-
402: 131,
|
|
2439
|
-
8222: 132,
|
|
2440
|
-
8230: 133,
|
|
2441
|
-
8224: 134,
|
|
2442
|
-
8225: 135,
|
|
2443
|
-
710: 136,
|
|
2444
|
-
8240: 137,
|
|
2445
|
-
352: 138,
|
|
2446
|
-
8249: 139,
|
|
2447
|
-
338: 140,
|
|
2448
|
-
381: 142,
|
|
2449
|
-
8216: 145,
|
|
2450
|
-
8217: 146,
|
|
2451
|
-
8220: 147,
|
|
2452
|
-
8221: 148,
|
|
2453
|
-
8226: 149,
|
|
2454
|
-
8211: 150,
|
|
2455
|
-
8212: 151,
|
|
2456
|
-
732: 152,
|
|
2457
|
-
8482: 153,
|
|
2458
|
-
353: 154,
|
|
2459
|
-
8250: 155,
|
|
2460
|
-
339: 156,
|
|
2461
|
-
382: 158,
|
|
2462
|
-
376: 159
|
|
2463
|
-
};
|
|
2464
|
-
function fixDoubleEncodedUtf8(str) {
|
|
2465
|
-
if (!str)
|
|
2466
|
-
return str;
|
|
2467
|
-
const bytes = [];
|
|
2468
|
-
for (const char of str) {
|
|
2469
|
-
const code = char.codePointAt(0);
|
|
2470
|
-
if (code < 128) {
|
|
2471
|
-
bytes.push(code);
|
|
2472
|
-
} else if (code <= 255) {
|
|
2473
|
-
bytes.push(code);
|
|
2474
|
-
} else {
|
|
2475
|
-
const cp1252Byte = CP1252_REVERSE[code];
|
|
2476
|
-
if (cp1252Byte !== undefined) {
|
|
2477
|
-
bytes.push(cp1252Byte);
|
|
2478
|
-
} else {
|
|
2479
|
-
return str;
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
try {
|
|
2484
|
-
const decoded = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes));
|
|
2485
|
-
if (decoded !== str) {
|
|
2486
|
-
return decoded;
|
|
2027
|
+
// src/utils/glob.ts
|
|
2028
|
+
function findClassEnd(glob, open) {
|
|
2029
|
+
for (let i = open + 1;i < glob.length; i++) {
|
|
2030
|
+
if (glob[i] === "\\") {
|
|
2031
|
+
i++;
|
|
2032
|
+
continue;
|
|
2487
2033
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
// src/adapters/identifier-quote.ts
|
|
2493
|
-
function quoteCharFor(dialect) {
|
|
2494
|
-
return dialect === "postgresql" ? '"' : "`";
|
|
2495
|
-
}
|
|
2496
|
-
function assertQuotable(name, what) {
|
|
2497
|
-
if (name.length === 0) {
|
|
2498
|
-
throw new Error(`${what} must not be empty`);
|
|
2499
|
-
}
|
|
2500
|
-
if (name.includes(String.fromCharCode(0))) {
|
|
2501
|
-
throw new Error(`${what} must not contain a NUL character`);
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
function quoteIdentifier(name, dialect) {
|
|
2505
|
-
assertQuotable(name, "identifier");
|
|
2506
|
-
const q = quoteCharFor(dialect);
|
|
2507
|
-
return `${q}${name.split(q).join(q + q)}${q}`;
|
|
2508
|
-
}
|
|
2509
|
-
function encodeEsPathSegment(value) {
|
|
2510
|
-
if (value.length === 0) {
|
|
2511
|
-
throw new Error("Elasticsearch path segment must not be empty");
|
|
2512
|
-
}
|
|
2513
|
-
if (value === "." || value === "..") {
|
|
2514
|
-
return value.replaceAll(".", "%2E");
|
|
2034
|
+
if (glob[i] === "]")
|
|
2035
|
+
return i;
|
|
2515
2036
|
}
|
|
2516
|
-
return
|
|
2037
|
+
return -1;
|
|
2517
2038
|
}
|
|
2518
|
-
function
|
|
2519
|
-
|
|
2520
|
-
|
|
2039
|
+
function isValidCharacterClass(body) {
|
|
2040
|
+
try {
|
|
2041
|
+
new RegExp(body);
|
|
2042
|
+
return true;
|
|
2043
|
+
} catch {
|
|
2044
|
+
return false;
|
|
2521
2045
|
}
|
|
2522
|
-
return expression.split(",").map((part) => encodeEsPathSegment(part)).join(",");
|
|
2523
2046
|
}
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2047
|
+
var parsedGlobs = new Map;
|
|
2048
|
+
function parseGlob(glob) {
|
|
2049
|
+
const memo = parsedGlobs.get(glob);
|
|
2050
|
+
if (memo !== undefined)
|
|
2051
|
+
return memo;
|
|
2052
|
+
const parsed = parseGlobUncached(glob);
|
|
2053
|
+
parsedGlobs.set(glob, parsed);
|
|
2054
|
+
return parsed;
|
|
2055
|
+
}
|
|
2056
|
+
function parseGlobUncached(glob) {
|
|
2057
|
+
const runs = [];
|
|
2058
|
+
let current = [];
|
|
2059
|
+
let leadingStar = false;
|
|
2060
|
+
let trailingStar = false;
|
|
2061
|
+
let sawStar = false;
|
|
2062
|
+
const endRun = () => {
|
|
2063
|
+
if (current.length > 0)
|
|
2064
|
+
runs.push(current);
|
|
2065
|
+
current = [];
|
|
2531
2066
|
};
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
const
|
|
2554
|
-
const
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
password: this.options.password,
|
|
2560
|
-
database: this.options.database,
|
|
2561
|
-
connectionTimeoutMillis: timeouts.connectMs,
|
|
2562
|
-
...timeouts.statementMs !== undefined && { statement_timeout: timeouts.statementMs }
|
|
2563
|
-
});
|
|
2564
|
-
await this.testConnection();
|
|
2565
|
-
try {
|
|
2566
|
-
const rawVersion = await this.getServerVersion();
|
|
2567
|
-
const result = checkDbVersion(rawVersion, "postgresql");
|
|
2568
|
-
warnIfUnsupported(result);
|
|
2569
|
-
} catch {}
|
|
2570
|
-
});
|
|
2571
|
-
}
|
|
2572
|
-
async disconnect() {
|
|
2573
|
-
try {
|
|
2574
|
-
if (this.client) {
|
|
2575
|
-
await this.client.release();
|
|
2576
|
-
this.client = null;
|
|
2577
|
-
}
|
|
2578
|
-
if (this.pool) {
|
|
2579
|
-
await this.pool.end();
|
|
2580
|
-
this.pool = null;
|
|
2581
|
-
}
|
|
2582
|
-
} catch {}
|
|
2583
|
-
}
|
|
2584
|
-
async testConnection() {
|
|
2585
|
-
requireConnected(this.pool);
|
|
2586
|
-
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
2587
|
-
const result = await this.execute("SELECT 1 as count");
|
|
2588
|
-
return result.rows.length > 0;
|
|
2589
|
-
});
|
|
2590
|
-
}
|
|
2591
|
-
async execute(sql, params) {
|
|
2592
|
-
const pool = requireConnected(this.pool);
|
|
2593
|
-
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
2594
|
-
const result = params ? await pool.query(sql, params) : await pool.query(sql);
|
|
2595
|
-
return {
|
|
2596
|
-
rows: result.rows,
|
|
2597
|
-
affectedRows: result.rowCount ?? 0
|
|
2598
|
-
};
|
|
2599
|
-
});
|
|
2600
|
-
}
|
|
2601
|
-
async getServerVersion() {
|
|
2602
|
-
const result = await this.execute("SHOW server_version");
|
|
2603
|
-
return result.rows[0]?.server_version ?? "unknown";
|
|
2604
|
-
}
|
|
2605
|
-
async listTables() {
|
|
2606
|
-
requireConnected(this.pool);
|
|
2607
|
-
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
2608
|
-
const query = `
|
|
2609
|
-
SELECT
|
|
2610
|
-
n.nspname as schema_name,
|
|
2611
|
-
c.relname as table_name,
|
|
2612
|
-
c.reltuples::bigint as estimated_rows,
|
|
2613
|
-
CASE c.relkind
|
|
2614
|
-
WHEN 'r' THEN 'table'
|
|
2615
|
-
WHEN 'v' THEN 'view'
|
|
2616
|
-
WHEN 'm' THEN 'view'
|
|
2617
|
-
ELSE 'table'
|
|
2618
|
-
END as table_type,
|
|
2619
|
-
(SELECT COUNT(*) FROM information_schema.columns ic
|
|
2620
|
-
WHERE ic.table_schema = 'public' AND ic.table_name = c.relname)::int as column_count
|
|
2621
|
-
FROM pg_class c
|
|
2622
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
2623
|
-
WHERE n.nspname = 'public'
|
|
2624
|
-
AND c.relkind IN ('r', 'v', 'm')
|
|
2625
|
-
ORDER BY c.relname
|
|
2626
|
-
`;
|
|
2627
|
-
const result = await this.execute(query);
|
|
2628
|
-
return result.rows.map((row) => ({
|
|
2629
|
-
name: row.table_name,
|
|
2630
|
-
schema: row.schema_name,
|
|
2631
|
-
columns: [],
|
|
2632
|
-
columnCount: row.column_count,
|
|
2633
|
-
rowCount: Math.max(0, row.estimated_rows || 0),
|
|
2634
|
-
engine: "PostgreSQL",
|
|
2635
|
-
estimatedRowCount: Math.max(0, row.estimated_rows || 0),
|
|
2636
|
-
tableType: row.table_type
|
|
2637
|
-
}));
|
|
2638
|
-
});
|
|
2639
|
-
}
|
|
2640
|
-
async getTableSchema(tableName, options) {
|
|
2641
|
-
requireConnected(this.pool);
|
|
2642
|
-
return withMappedConnectionError("postgresql", this.options, async () => {
|
|
2643
|
-
const columnQuery = `
|
|
2644
|
-
SELECT
|
|
2645
|
-
c.column_name as name,
|
|
2646
|
-
c.data_type as type,
|
|
2647
|
-
c.is_nullable = 'YES' as nullable,
|
|
2648
|
-
c.column_default as default_value,
|
|
2649
|
-
(
|
|
2650
|
-
SELECT EXISTS(
|
|
2651
|
-
SELECT 1 FROM information_schema.table_constraints tc
|
|
2652
|
-
JOIN information_schema.key_column_usage kcu
|
|
2653
|
-
ON tc.constraint_catalog = kcu.constraint_catalog
|
|
2654
|
-
AND tc.constraint_schema = kcu.constraint_schema
|
|
2655
|
-
AND tc.constraint_name = kcu.constraint_name
|
|
2656
|
-
WHERE tc.table_catalog = c.table_catalog
|
|
2657
|
-
AND tc.table_schema = c.table_schema
|
|
2658
|
-
AND tc.table_name = c.table_name
|
|
2659
|
-
AND kcu.table_catalog = c.table_catalog
|
|
2660
|
-
AND kcu.table_schema = c.table_schema
|
|
2661
|
-
AND kcu.table_name = c.table_name
|
|
2662
|
-
AND tc.constraint_type = 'PRIMARY KEY'
|
|
2663
|
-
AND kcu.column_name = c.column_name
|
|
2664
|
-
)
|
|
2665
|
-
) as is_primary_key,
|
|
2666
|
-
(c.column_default LIKE 'nextval%') as auto_increment,
|
|
2667
|
-
pgd.description as comment
|
|
2668
|
-
FROM information_schema.columns c
|
|
2669
|
-
LEFT JOIN pg_catalog.pg_statio_all_tables st
|
|
2670
|
-
ON st.schemaname = c.table_schema AND st.relname = c.table_name
|
|
2671
|
-
LEFT JOIN pg_catalog.pg_description pgd
|
|
2672
|
-
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
|
2673
|
-
WHERE c.table_name = $1
|
|
2674
|
-
AND c.table_schema = 'public'
|
|
2675
|
-
ORDER BY c.ordinal_position
|
|
2676
|
-
`;
|
|
2677
|
-
const columnResult = await this.execute(columnQuery, [tableName]);
|
|
2678
|
-
const columns = columnResult.rows;
|
|
2679
|
-
const enumQuery = `
|
|
2680
|
-
SELECT
|
|
2681
|
-
c.column_name as name,
|
|
2682
|
-
array_agg(e.enumlabel ORDER BY e.enumsortorder) as enum_values
|
|
2683
|
-
FROM information_schema.columns c
|
|
2684
|
-
JOIN pg_catalog.pg_type AS enum_type
|
|
2685
|
-
ON enum_type.typname = c.udt_name
|
|
2686
|
-
JOIN pg_catalog.pg_namespace AS enum_schema
|
|
2687
|
-
ON enum_schema.oid = enum_type.typnamespace
|
|
2688
|
-
AND enum_schema.nspname = c.udt_schema
|
|
2689
|
-
JOIN pg_catalog.pg_enum AS e ON e.enumtypid = enum_type.oid
|
|
2690
|
-
WHERE c.table_name = $1
|
|
2691
|
-
AND c.table_schema = 'public'
|
|
2692
|
-
GROUP BY c.column_name
|
|
2693
|
-
`;
|
|
2694
|
-
const enumResult = await this.execute(enumQuery, [tableName]);
|
|
2695
|
-
const enumResults = enumResult.rows;
|
|
2696
|
-
const enumMap = new Map;
|
|
2697
|
-
for (const row of enumResults) {
|
|
2698
|
-
enumMap.set(row.name, Array.isArray(row.enum_values) ? row.enum_values : []);
|
|
2699
|
-
}
|
|
2700
|
-
const fkQuery = `
|
|
2701
|
-
SELECT
|
|
2702
|
-
constraint_info.conname as name,
|
|
2703
|
-
array_agg(source_column.attname ORDER BY source_key.ordinality) as columns,
|
|
2704
|
-
referenced_schema.nspname as ref_schema,
|
|
2705
|
-
referenced_table.relname as ref_table,
|
|
2706
|
-
array_agg(referenced_column.attname ORDER BY source_key.ordinality) as ref_columns
|
|
2707
|
-
FROM pg_catalog.pg_constraint AS constraint_info
|
|
2708
|
-
JOIN pg_catalog.pg_class AS source_table
|
|
2709
|
-
ON source_table.oid = constraint_info.conrelid
|
|
2710
|
-
JOIN pg_catalog.pg_namespace AS source_schema
|
|
2711
|
-
ON source_schema.oid = source_table.relnamespace
|
|
2712
|
-
JOIN pg_catalog.pg_class AS referenced_table
|
|
2713
|
-
ON referenced_table.oid = constraint_info.confrelid
|
|
2714
|
-
JOIN pg_catalog.pg_namespace AS referenced_schema
|
|
2715
|
-
ON referenced_schema.oid = referenced_table.relnamespace
|
|
2716
|
-
JOIN LATERAL unnest(constraint_info.conkey) WITH ORDINALITY
|
|
2717
|
-
AS source_key(attnum, ordinality) ON TRUE
|
|
2718
|
-
JOIN LATERAL unnest(constraint_info.confkey) WITH ORDINALITY
|
|
2719
|
-
AS referenced_key(attnum, ordinality)
|
|
2720
|
-
ON referenced_key.ordinality = source_key.ordinality
|
|
2721
|
-
JOIN pg_catalog.pg_attribute AS source_column
|
|
2722
|
-
ON source_column.attrelid = constraint_info.conrelid
|
|
2723
|
-
AND source_column.attnum = source_key.attnum
|
|
2724
|
-
JOIN pg_catalog.pg_attribute AS referenced_column
|
|
2725
|
-
ON referenced_column.attrelid = constraint_info.confrelid
|
|
2726
|
-
AND referenced_column.attnum = referenced_key.attnum
|
|
2727
|
-
WHERE constraint_info.contype = 'f'
|
|
2728
|
-
AND source_table.relname = $1
|
|
2729
|
-
AND source_schema.nspname = 'public'
|
|
2730
|
-
GROUP BY
|
|
2731
|
-
constraint_info.oid,
|
|
2732
|
-
constraint_info.conname,
|
|
2733
|
-
referenced_schema.nspname,
|
|
2734
|
-
referenced_table.relname
|
|
2735
|
-
ORDER BY constraint_info.oid
|
|
2736
|
-
`;
|
|
2737
|
-
const fkResult = await this.execute(fkQuery, [tableName]);
|
|
2738
|
-
const fkResults = fkResult.rows;
|
|
2739
|
-
const fkMap = new Map;
|
|
2740
|
-
for (const fk of fkResults) {
|
|
2741
|
-
if (fk.columns.length === 1 && fk.ref_columns.length === 1) {
|
|
2742
|
-
fkMap.set(fk.columns[0], { table: fk.ref_table, column: fk.ref_columns[0] });
|
|
2743
|
-
}
|
|
2067
|
+
for (let i = 0;i < glob.length; i++) {
|
|
2068
|
+
const c = glob[i];
|
|
2069
|
+
if (c === "\\" && i + 1 < glob.length) {
|
|
2070
|
+
trailingStar = false;
|
|
2071
|
+
current.push({ kind: "literal", char: glob[++i] });
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
if (c === "*") {
|
|
2075
|
+
if (!sawStar && current.length === 0)
|
|
2076
|
+
leadingStar = true;
|
|
2077
|
+
sawStar = true;
|
|
2078
|
+
trailingStar = true;
|
|
2079
|
+
endRun();
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
trailingStar = false;
|
|
2083
|
+
if (c === "?") {
|
|
2084
|
+
current.push({ kind: "any" });
|
|
2085
|
+
continue;
|
|
2086
|
+
}
|
|
2087
|
+
if (c === "[") {
|
|
2088
|
+
const end = findClassEnd(glob, i);
|
|
2089
|
+
const body = end === -1 ? "" : glob.slice(i, end + 1);
|
|
2090
|
+
if (body !== "" && isValidCharacterClass(body)) {
|
|
2091
|
+
current.push({ kind: "class", test: new RegExp(`^${body}$`, "s") });
|
|
2092
|
+
i = end;
|
|
2093
|
+
continue;
|
|
2744
2094
|
}
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
ix.indisunique as is_unique
|
|
2750
|
-
FROM pg_index ix
|
|
2751
|
-
JOIN pg_class t ON t.oid = ix.indrelid
|
|
2752
|
-
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
2753
|
-
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
2754
|
-
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
|
|
2755
|
-
WHERE t.relname = $1
|
|
2756
|
-
AND n.nspname = 'public'
|
|
2757
|
-
AND NOT ix.indisprimary
|
|
2758
|
-
GROUP BY i.relname, ix.indisunique
|
|
2759
|
-
`;
|
|
2760
|
-
const indexResult = await this.execute(indexQuery, [tableName]);
|
|
2761
|
-
const indexResults = indexResult.rows;
|
|
2762
|
-
const estimateQuery = `
|
|
2763
|
-
SELECT source_table.reltuples::bigint as estimated_rows
|
|
2764
|
-
FROM pg_catalog.pg_class AS source_table
|
|
2765
|
-
JOIN pg_catalog.pg_namespace AS source_schema
|
|
2766
|
-
ON source_schema.oid = source_table.relnamespace
|
|
2767
|
-
WHERE source_table.relname = $1
|
|
2768
|
-
AND source_schema.nspname = 'public'
|
|
2769
|
-
`;
|
|
2770
|
-
const estimateResult = await this.execute(estimateQuery, [
|
|
2771
|
-
tableName
|
|
2772
|
-
]);
|
|
2773
|
-
const estimateResults = estimateResult.rows;
|
|
2774
|
-
const pkQuery = `
|
|
2775
|
-
SELECT
|
|
2776
|
-
array_agg(primary_key_column.attname ORDER BY primary_key.ordinality) as columns
|
|
2777
|
-
FROM pg_catalog.pg_index AS index_info
|
|
2778
|
-
JOIN pg_catalog.pg_class AS source_table
|
|
2779
|
-
ON source_table.oid = index_info.indrelid
|
|
2780
|
-
JOIN pg_catalog.pg_namespace AS source_schema
|
|
2781
|
-
ON source_schema.oid = source_table.relnamespace
|
|
2782
|
-
JOIN LATERAL unnest(index_info.indkey) WITH ORDINALITY
|
|
2783
|
-
AS primary_key(attnum, ordinality) ON TRUE
|
|
2784
|
-
JOIN pg_catalog.pg_attribute AS primary_key_column
|
|
2785
|
-
ON primary_key_column.attrelid = source_table.oid
|
|
2786
|
-
AND primary_key_column.attnum = primary_key.attnum
|
|
2787
|
-
WHERE index_info.indisprimary
|
|
2788
|
-
AND source_table.relname = $1
|
|
2789
|
-
AND source_schema.nspname = 'public'
|
|
2790
|
-
`;
|
|
2791
|
-
const pkResult = await this.execute(pkQuery, [tableName]);
|
|
2792
|
-
const pkResults = pkResult.rows;
|
|
2793
|
-
const qualifiedTableName = `${quoteIdentifier("public", "postgresql")}.${quoteIdentifier(tableName, "postgresql")}`;
|
|
2794
|
-
const exactRowCount = options?.exactRowCount === false ? undefined : (await this.execute(`SELECT COUNT(*) as count FROM ${qualifiedTableName}`)).rows[0]?.count;
|
|
2795
|
-
const primaryKeyArray = Array.isArray(pkResults[0]?.columns) ? pkResults[0].columns : [];
|
|
2796
|
-
const safeForeignKeys = fkResults.map((fk) => ({
|
|
2797
|
-
name: fk.name,
|
|
2798
|
-
columns: Array.isArray(fk.columns) ? fk.columns : [],
|
|
2799
|
-
refSchema: fk.ref_schema,
|
|
2800
|
-
refTable: fk.ref_table,
|
|
2801
|
-
refColumns: Array.isArray(fk.ref_columns) ? fk.ref_columns : []
|
|
2802
|
-
}));
|
|
2803
|
-
const schema = {
|
|
2804
|
-
name: tableName,
|
|
2805
|
-
schema: "public",
|
|
2806
|
-
columns: columns.map((col) => ({
|
|
2807
|
-
name: col.name,
|
|
2808
|
-
type: col.type,
|
|
2809
|
-
nullable: col.nullable,
|
|
2810
|
-
default: col.default_value || undefined,
|
|
2811
|
-
primaryKey: col.is_primary_key,
|
|
2812
|
-
foreignKey: fkMap.get(col.name),
|
|
2813
|
-
autoIncrement: col.auto_increment,
|
|
2814
|
-
comment: col.comment ? fixDoubleEncodedUtf8(col.comment) : null,
|
|
2815
|
-
enumValues: enumMap.get(col.name)
|
|
2816
|
-
})),
|
|
2817
|
-
rowCount: exactRowCount ?? Math.max(0, estimateResults[0]?.estimated_rows || 0),
|
|
2818
|
-
engine: "PostgreSQL",
|
|
2819
|
-
primaryKey: primaryKeyArray,
|
|
2820
|
-
foreignKeys: safeForeignKeys,
|
|
2821
|
-
indexes: indexResults.map((idx) => ({
|
|
2822
|
-
name: idx.name,
|
|
2823
|
-
columns: Array.isArray(idx.columns) ? idx.columns : [],
|
|
2824
|
-
unique: idx.is_unique
|
|
2825
|
-
})),
|
|
2826
|
-
estimatedRowCount: Math.max(0, estimateResults[0]?.estimated_rows || 0)
|
|
2827
|
-
};
|
|
2828
|
-
return schema;
|
|
2829
|
-
});
|
|
2095
|
+
current.push({ kind: "literal", char: "[" });
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
current.push({ kind: "literal", char: c });
|
|
2830
2099
|
}
|
|
2100
|
+
endRun();
|
|
2101
|
+
return { runs, leadingStar, trailingStar, anchored: !sawStar };
|
|
2831
2102
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
class
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
constructor(options) {
|
|
2846
|
-
this.options = options;
|
|
2847
|
-
this.system = options.system === "mariadb" ? "mariadb" : "mysql";
|
|
2848
|
-
if (options.port < 1 || options.port > 65535) {
|
|
2849
|
-
throw new Error(`Invalid port number: ${options.port}`);
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
2852
|
-
async connect() {
|
|
2853
|
-
await withMappedConnectionError(this.system, this.options, async () => {
|
|
2854
|
-
const timeouts = resolveTimeoutPolicy(this.options);
|
|
2855
|
-
const { default: driver } = await import("mysql2/promise");
|
|
2856
|
-
this.db = await driver.createConnection({
|
|
2857
|
-
host: this.options.host,
|
|
2858
|
-
port: this.options.port,
|
|
2859
|
-
user: this.options.user,
|
|
2860
|
-
password: this.options.password || undefined,
|
|
2861
|
-
database: this.options.database,
|
|
2862
|
-
charset: "utf8mb4",
|
|
2863
|
-
connectTimeout: timeouts.connectMs
|
|
2864
|
-
});
|
|
2865
|
-
await this.db.execute("SET NAMES utf8mb4");
|
|
2866
|
-
if (timeouts.statementMs !== undefined) {
|
|
2867
|
-
try {
|
|
2868
|
-
await this.db.query(statementTimeoutSql(this.system, timeouts.statementMs));
|
|
2869
|
-
} catch {}
|
|
2870
|
-
}
|
|
2871
|
-
await this.testConnection();
|
|
2872
|
-
try {
|
|
2873
|
-
const rawVersion = await this.getServerVersion();
|
|
2874
|
-
const result = checkDbVersion(rawVersion, this.system);
|
|
2875
|
-
warnIfUnsupported(result);
|
|
2876
|
-
} catch {}
|
|
2877
|
-
});
|
|
2878
|
-
}
|
|
2879
|
-
async disconnect() {
|
|
2880
|
-
try {
|
|
2881
|
-
if (this.db) {
|
|
2882
|
-
await this.db.end();
|
|
2883
|
-
this.db = null;
|
|
2884
|
-
}
|
|
2885
|
-
} catch {}
|
|
2886
|
-
}
|
|
2887
|
-
async testConnection() {
|
|
2888
|
-
requireConnected(this.db);
|
|
2889
|
-
return withMappedConnectionError(this.system, this.options, async () => {
|
|
2890
|
-
const result = await this.execute("SELECT 1 as count");
|
|
2891
|
-
return result.rows.length > 0;
|
|
2892
|
-
});
|
|
2103
|
+
function runMatchesAt(run, text, at) {
|
|
2104
|
+
if (at + run.length > text.length)
|
|
2105
|
+
return false;
|
|
2106
|
+
for (let i = 0;i < run.length; i++) {
|
|
2107
|
+
const token = run[i];
|
|
2108
|
+
const ch = text[at + i];
|
|
2109
|
+
if (token.kind === "literal") {
|
|
2110
|
+
if (token.char !== ch)
|
|
2111
|
+
return false;
|
|
2112
|
+
} else if (token.kind === "class") {
|
|
2113
|
+
if (!token.test.test(ch))
|
|
2114
|
+
return false;
|
|
2115
|
+
}
|
|
2893
2116
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
affectedRows: result.length
|
|
2902
|
-
};
|
|
2903
|
-
}
|
|
2904
|
-
const header = result;
|
|
2905
|
-
return {
|
|
2906
|
-
rows: [],
|
|
2907
|
-
affectedRows: header.affectedRows || 0,
|
|
2908
|
-
lastInsertId: header.insertId
|
|
2909
|
-
};
|
|
2910
|
-
});
|
|
2117
|
+
return true;
|
|
2118
|
+
}
|
|
2119
|
+
function globMatches(glob, text) {
|
|
2120
|
+
const { runs, leadingStar, trailingStar, anchored } = parseGlob(glob);
|
|
2121
|
+
if (anchored) {
|
|
2122
|
+
const run = runs[0] ?? [];
|
|
2123
|
+
return text.length === run.length && runMatchesAt(run, text, 0);
|
|
2911
2124
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
t.ENGINE as engine,
|
|
2924
|
-
t.TABLE_TYPE as table_type,
|
|
2925
|
-
COUNT(c.COLUMN_NAME) as column_count
|
|
2926
|
-
FROM information_schema.TABLES t
|
|
2927
|
-
LEFT JOIN information_schema.COLUMNS c
|
|
2928
|
-
ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
|
|
2929
|
-
WHERE t.TABLE_SCHEMA = DATABASE()
|
|
2930
|
-
GROUP BY t.TABLE_NAME, t.TABLE_ROWS, t.ENGINE, t.TABLE_TYPE
|
|
2931
|
-
ORDER BY t.TABLE_NAME
|
|
2932
|
-
`;
|
|
2933
|
-
const result = await this.execute(query);
|
|
2934
|
-
return result.rows.map((row) => ({
|
|
2935
|
-
name: row.table_name,
|
|
2936
|
-
columns: [],
|
|
2937
|
-
columnCount: row.column_count,
|
|
2938
|
-
rowCount: row.row_count || 0,
|
|
2939
|
-
engine: row.engine,
|
|
2940
|
-
estimatedRowCount: row.row_count || 0,
|
|
2941
|
-
tableType: row.table_type === "VIEW" ? "view" : "table"
|
|
2942
|
-
}));
|
|
2943
|
-
});
|
|
2125
|
+
if (runs.length === 0)
|
|
2126
|
+
return true;
|
|
2127
|
+
let cursor = 0;
|
|
2128
|
+
let first = 0;
|
|
2129
|
+
let last = runs.length;
|
|
2130
|
+
if (!leadingStar) {
|
|
2131
|
+
const head = runs[0];
|
|
2132
|
+
if (!runMatchesAt(head, text, 0))
|
|
2133
|
+
return false;
|
|
2134
|
+
cursor = head.length;
|
|
2135
|
+
first = 1;
|
|
2944
2136
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
ORDER BY ORDINAL_POSITION
|
|
2961
|
-
`;
|
|
2962
|
-
const columnResult = await this.execute(columnQuery, [tableName]);
|
|
2963
|
-
const columns = columnResult.rows;
|
|
2964
|
-
const fkQuery = `
|
|
2965
|
-
SELECT
|
|
2966
|
-
rc.CONSTRAINT_NAME as name,
|
|
2967
|
-
GROUP_CONCAT(kcu.COLUMN_NAME) as columns,
|
|
2968
|
-
rc.REFERENCED_TABLE_NAME as ref_table,
|
|
2969
|
-
GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME) as ref_columns
|
|
2970
|
-
FROM information_schema.REFERENTIAL_CONSTRAINTS rc
|
|
2971
|
-
JOIN information_schema.KEY_COLUMN_USAGE kcu
|
|
2972
|
-
ON rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND rc.TABLE_NAME = kcu.TABLE_NAME
|
|
2973
|
-
WHERE kcu.TABLE_NAME = ? AND rc.CONSTRAINT_SCHEMA = DATABASE()
|
|
2974
|
-
GROUP BY rc.CONSTRAINT_NAME, rc.REFERENCED_TABLE_NAME
|
|
2975
|
-
`;
|
|
2976
|
-
const fkResult = await this.execute(fkQuery, [tableName]);
|
|
2977
|
-
const fkResults = fkResult.rows;
|
|
2978
|
-
const fkMap = new Map;
|
|
2979
|
-
for (const fk of fkResults) {
|
|
2980
|
-
const cols = fk.columns.split(",").map((c) => c.trim());
|
|
2981
|
-
const refCols = fk.ref_columns.split(",").map((c) => c.trim());
|
|
2982
|
-
if (cols.length === 1 && refCols.length === 1) {
|
|
2983
|
-
fkMap.set(cols[0], { table: fk.ref_table, column: refCols[0] });
|
|
2984
|
-
}
|
|
2137
|
+
if (!trailingStar) {
|
|
2138
|
+
const tail = runs[last - 1];
|
|
2139
|
+
const at = text.length - tail.length;
|
|
2140
|
+
if (at < cursor || !runMatchesAt(tail, text, at))
|
|
2141
|
+
return false;
|
|
2142
|
+
last -= 1;
|
|
2143
|
+
}
|
|
2144
|
+
const limit = trailingStar ? text.length : text.length - runs[runs.length - 1].length;
|
|
2145
|
+
for (let r = first;r < last; r++) {
|
|
2146
|
+
const run = runs[r];
|
|
2147
|
+
let found = -1;
|
|
2148
|
+
for (let at = cursor;at + run.length <= limit; at++) {
|
|
2149
|
+
if (runMatchesAt(run, text, at)) {
|
|
2150
|
+
found = at;
|
|
2151
|
+
break;
|
|
2985
2152
|
}
|
|
2986
|
-
const primaryKeyColumns = columns.filter((col) => col.is_primary_key).map((col) => col.name);
|
|
2987
|
-
const indexQuery = `
|
|
2988
|
-
SELECT
|
|
2989
|
-
INDEX_NAME as name,
|
|
2990
|
-
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
|
|
2991
|
-
NOT NON_UNIQUE as is_unique
|
|
2992
|
-
FROM information_schema.STATISTICS
|
|
2993
|
-
WHERE TABLE_SCHEMA = DATABASE()
|
|
2994
|
-
AND TABLE_NAME = ?
|
|
2995
|
-
AND INDEX_NAME != 'PRIMARY'
|
|
2996
|
-
GROUP BY INDEX_NAME, NON_UNIQUE
|
|
2997
|
-
`;
|
|
2998
|
-
const indexResult = await this.execute(indexQuery, [tableName]);
|
|
2999
|
-
const indexResults = indexResult.rows;
|
|
3000
|
-
const tableMetaQuery = `
|
|
3001
|
-
SELECT ENGINE as engine, TABLE_ROWS as estimated_rows
|
|
3002
|
-
FROM information_schema.TABLES
|
|
3003
|
-
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
|
|
3004
|
-
`;
|
|
3005
|
-
const tableMetaResult = await this.execute(tableMetaQuery, [tableName]);
|
|
3006
|
-
const tableMeta = tableMetaResult.rows[0];
|
|
3007
|
-
const estimatedRowCount = Math.max(0, tableMeta?.estimated_rows || 0);
|
|
3008
|
-
const exactRowCount = options?.exactRowCount === false ? undefined : (await this.execute(`SELECT COUNT(*) as count FROM ${quoteIdentifier(tableName, this.system)}`)).rows[0]?.count;
|
|
3009
|
-
const schema = {
|
|
3010
|
-
name: tableName,
|
|
3011
|
-
columns: columns.map((col) => ({
|
|
3012
|
-
name: col.name,
|
|
3013
|
-
type: col.type,
|
|
3014
|
-
nullable: col.nullable,
|
|
3015
|
-
default: col.default_value || undefined,
|
|
3016
|
-
primaryKey: col.is_primary_key,
|
|
3017
|
-
foreignKey: fkMap.get(col.name),
|
|
3018
|
-
autoIncrement: col.auto_increment,
|
|
3019
|
-
comment: col.comment ? fixDoubleEncodedUtf8(col.comment) : null,
|
|
3020
|
-
enumValues: parseEnumValues(col.type)
|
|
3021
|
-
})),
|
|
3022
|
-
rowCount: exactRowCount ?? estimatedRowCount,
|
|
3023
|
-
engine: tableMeta?.engine || "MySQL",
|
|
3024
|
-
primaryKey: primaryKeyColumns,
|
|
3025
|
-
foreignKeys: fkResults.map((fk) => ({
|
|
3026
|
-
name: fk.name,
|
|
3027
|
-
columns: fk.columns.split(",").map((c) => c.trim()),
|
|
3028
|
-
refTable: fk.ref_table,
|
|
3029
|
-
refColumns: fk.ref_columns.split(",").map((c) => c.trim())
|
|
3030
|
-
})),
|
|
3031
|
-
indexes: indexResults.map((idx) => ({
|
|
3032
|
-
name: idx.name,
|
|
3033
|
-
columns: idx.columns.split(",").map((c) => c.trim()),
|
|
3034
|
-
unique: idx.is_unique
|
|
3035
|
-
})),
|
|
3036
|
-
estimatedRowCount
|
|
3037
|
-
};
|
|
3038
|
-
return schema;
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
3041
|
-
}
|
|
3042
|
-
|
|
3043
|
-
// src/adapters/mongodb-adapter.ts
|
|
3044
|
-
import { resolveSrv, resolveTxt } from "dns/promises";
|
|
3045
|
-
|
|
3046
|
-
// src/adapters/server-side-script.ts
|
|
3047
|
-
class ServerSideScriptRejection extends Error {
|
|
3048
|
-
operator;
|
|
3049
|
-
constructor(message, operator) {
|
|
3050
|
-
super(message);
|
|
3051
|
-
this.operator = operator;
|
|
3052
|
-
this.name = "ServerSideScriptRejection";
|
|
3053
|
-
Object.setPrototypeOf(this, ServerSideScriptRejection.prototype);
|
|
3054
|
-
}
|
|
3055
|
-
}
|
|
3056
|
-
var MONGO_SCRIPT_OPERATORS = ["$where", "$function", "$accumulator"];
|
|
3057
|
-
var ES_SCRIPT_KEYS = ["script", "script_fields"];
|
|
3058
|
-
function findKey(value, keys) {
|
|
3059
|
-
if (value === null || typeof value !== "object")
|
|
3060
|
-
return;
|
|
3061
|
-
if (Array.isArray(value)) {
|
|
3062
|
-
for (const item of value) {
|
|
3063
|
-
const found = findKey(item, keys);
|
|
3064
|
-
if (found)
|
|
3065
|
-
return found;
|
|
3066
2153
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
for (const key of keys) {
|
|
3071
|
-
if (key in record)
|
|
3072
|
-
return key;
|
|
3073
|
-
}
|
|
3074
|
-
for (const nested of Object.values(record)) {
|
|
3075
|
-
const found = findKey(nested, keys);
|
|
3076
|
-
if (found)
|
|
3077
|
-
return found;
|
|
2154
|
+
if (found === -1)
|
|
2155
|
+
return false;
|
|
2156
|
+
cursor = found + run.length;
|
|
3078
2157
|
}
|
|
3079
|
-
return;
|
|
2158
|
+
return true;
|
|
3080
2159
|
}
|
|
3081
|
-
function
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
function assertNoElasticsearchScript(body) {
|
|
3088
|
-
const key = findKey(body, ES_SCRIPT_KEYS);
|
|
3089
|
-
if (!key)
|
|
3090
|
-
return;
|
|
3091
|
-
throw new ServerSideScriptRejection(`Elasticsearch server-side script rejected: '${key}' executes script code on the cluster. ` + `Rewrite the query without it.`, key);
|
|
3092
|
-
}
|
|
3093
|
-
|
|
3094
|
-
// src/adapters/mongodb-adapter.ts
|
|
3095
|
-
class MongoDBAdapter {
|
|
3096
|
-
options;
|
|
3097
|
-
ClientClass;
|
|
3098
|
-
client = null;
|
|
3099
|
-
constructor(options, ClientClass = null) {
|
|
3100
|
-
this.options = options;
|
|
3101
|
-
this.ClientClass = ClientClass;
|
|
3102
|
-
}
|
|
3103
|
-
async resolveClientClass() {
|
|
3104
|
-
if (this.ClientClass)
|
|
3105
|
-
return this.ClientClass;
|
|
3106
|
-
const { MongoClient } = await import("mongodb");
|
|
3107
|
-
return MongoClient;
|
|
3108
|
-
}
|
|
3109
|
-
buildUri() {
|
|
3110
|
-
if (this.options.uri)
|
|
3111
|
-
return this.options.uri;
|
|
3112
|
-
const { user, password, host, port, database, authSource, replicaSet, tls, srv } = this.options;
|
|
3113
|
-
if (!host) {
|
|
3114
|
-
throw new ConnectionError("UNKNOWN", "MongoDB host \u672A\u8A2D\u5B9A", [
|
|
3115
|
-
"\u8ACB\u586B\u5BEB host\uFF0C\u6216\u6539\u7528 uri \u6B04\u4F4D\u6307\u5B9A\u5B8C\u6574\u9023\u7DDA\u5B57\u4E32"
|
|
3116
|
-
]);
|
|
3117
|
-
}
|
|
3118
|
-
const isBracketedIpv6 = /^\[[0-9a-f:]+\]$/i.test(host);
|
|
3119
|
-
if (!isBracketedIpv6 && /[/@?#:\s\\]/.test(host)) {
|
|
3120
|
-
throw new ConnectionError("UNKNOWN", `MongoDB host \u542B\u6709\u975E\u6CD5\u5B57\u5143: ${host}`, [
|
|
3121
|
-
"host \u53EA\u61C9\u5305\u542B\u4E3B\u6A5F\u540D\u7A31\u6216 IP\uFF0C\u4E0D\u8981\u542B /\u3001@\u3001?\u3001#\u3001: \u6216\u7A7A\u767D",
|
|
3122
|
-
"\u57E0\u865F\u8ACB\u586B\u5728 port \u6B04\u4F4D\uFF0C\u4E0D\u8981\u4F75\u9032 host",
|
|
3123
|
-
"IPv6 \u4F4D\u5740\u8ACB\u52A0\u65B9\u62EC\u865F\uFF0C\u4F8B\u5982 [::1]",
|
|
3124
|
-
"\u82E5\u8981\u6307\u5B9A\u5B8C\u6574\u9023\u7DDA\u5B57\u4E32\uFF0C\u8ACB\u6539\u7528 uri \u6B04\u4F4D"
|
|
3125
|
-
]);
|
|
3126
|
-
}
|
|
3127
|
-
if (user && !password) {
|
|
3128
|
-
throw new ConnectionError("UNKNOWN", "\u5DF2\u6307\u5B9A user \u4F46\u672A\u63D0\u4F9B password", [
|
|
3129
|
-
'\u8ACB\u88DC\u4E0A password\uFF0C\u6216\u6539\u7528\u74B0\u5883\u8B8A\u6578\u53C3\u7167 {"$env": "..."}',
|
|
3130
|
-
"\u82E5\u78BA\u5B9A\u8981\u4EE5\u7121\u8A8D\u8B49\u65B9\u5F0F\u9023\u7DDA\uFF0C\u8ACB\u4E00\u4F75\u6E05\u7A7A user"
|
|
3131
|
-
]);
|
|
3132
|
-
}
|
|
3133
|
-
const userInfo = user ? `${encodeURIComponent(user)}:${encodeURIComponent(password)}@` : "";
|
|
3134
|
-
const scheme = srv ? "mongodb+srv://" : "mongodb://";
|
|
3135
|
-
const authority = srv ? host : `${host}:${port}`;
|
|
3136
|
-
const path = database ? `/${encodeURIComponent(database)}` : "/";
|
|
3137
|
-
const query = new URLSearchParams;
|
|
3138
|
-
if (user)
|
|
3139
|
-
query.set("authSource", authSource || "admin");
|
|
3140
|
-
if (replicaSet)
|
|
3141
|
-
query.set("replicaSet", replicaSet);
|
|
3142
|
-
if (tls !== undefined)
|
|
3143
|
-
query.set("tls", String(tls));
|
|
3144
|
-
const search = query.toString();
|
|
3145
|
-
return `${scheme}${userInfo}${authority}${path}${search ? `?${search}` : ""}`;
|
|
3146
|
-
}
|
|
3147
|
-
parseTxtRecords(records) {
|
|
3148
|
-
const combined = records.flat().map((record) => record.replace(/^"|"$/g, "")).join("&");
|
|
3149
|
-
if (!combined)
|
|
3150
|
-
return {};
|
|
3151
|
-
const params = new URLSearchParams(combined);
|
|
3152
|
-
return Object.fromEntries(params.entries());
|
|
3153
|
-
}
|
|
3154
|
-
async resolveSrvHosts(hostname) {
|
|
3155
|
-
const srvName = `_mongodb._tcp.${hostname}`;
|
|
3156
|
-
try {
|
|
3157
|
-
const records = await resolveSrv(srvName);
|
|
3158
|
-
if (!records.length) {
|
|
3159
|
-
throw new Error(`No SRV records found for ${srvName}`);
|
|
3160
|
-
}
|
|
3161
|
-
return records.map((record) => {
|
|
3162
|
-
const target = String(record.name || record.target || "").replace(/\.$/, "");
|
|
3163
|
-
return `${target}:${record.port}`;
|
|
3164
|
-
});
|
|
3165
|
-
} catch (error) {
|
|
3166
|
-
const code = error?.code;
|
|
3167
|
-
if (!["ECONNREFUSED", "ENOTFOUND", "ETIMEOUT", "ETIMEDOUT"].includes(code ?? "")) {
|
|
3168
|
-
throw error;
|
|
3169
|
-
}
|
|
3170
|
-
}
|
|
3171
|
-
const response = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(srvName)}&type=SRV`);
|
|
3172
|
-
if (!response.ok) {
|
|
3173
|
-
throw new Error(`SRV lookup failed with HTTP ${response.status}`);
|
|
2160
|
+
function globNeverMatches(glob) {
|
|
2161
|
+
for (let i = 0;i < glob.length; i++) {
|
|
2162
|
+
const c = glob[i];
|
|
2163
|
+
if (c === "\\") {
|
|
2164
|
+
i++;
|
|
2165
|
+
continue;
|
|
3174
2166
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
2167
|
+
if (c !== "[")
|
|
2168
|
+
continue;
|
|
2169
|
+
const end = findClassEnd(glob, i);
|
|
2170
|
+
if (end === -1) {
|
|
2171
|
+
return "unclosed character class";
|
|
3178
2172
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
return
|
|
3184
|
-
|
|
2173
|
+
const body = glob.slice(i, end + 1);
|
|
2174
|
+
if (body === "[]" || body === "[^]")
|
|
2175
|
+
return "empty character class matches nothing";
|
|
2176
|
+
if (!isValidCharacterClass(body))
|
|
2177
|
+
return "invalid character class";
|
|
2178
|
+
i = end;
|
|
3185
2179
|
}
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
if (!response.ok) {
|
|
3198
|
-
throw new Error(`TXT lookup failed with HTTP ${response.status}`);
|
|
2180
|
+
return null;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
// src/core/mongo/path-matcher.ts
|
|
2184
|
+
function compilePatterns(raw) {
|
|
2185
|
+
const patterns = [];
|
|
2186
|
+
const rejected = [];
|
|
2187
|
+
for (const entry of raw) {
|
|
2188
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
2189
|
+
rejected.push({ raw: String(entry ?? ""), reason: "must be a non-empty string" });
|
|
2190
|
+
continue;
|
|
3199
2191
|
}
|
|
3200
|
-
const
|
|
3201
|
-
if (
|
|
3202
|
-
|
|
2192
|
+
const segments = entry.split(".");
|
|
2193
|
+
if (segments.some((s) => s.length === 0)) {
|
|
2194
|
+
rejected.push({ raw: entry, reason: "empty path segment" });
|
|
2195
|
+
continue;
|
|
3203
2196
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
const url = new URL(canonical);
|
|
3212
|
-
const hosts = await this.resolveSrvHosts(url.hostname);
|
|
3213
|
-
const txtOptions = await this.resolveTxtOptions(url.hostname);
|
|
3214
|
-
const query = new URLSearchParams(url.searchParams);
|
|
3215
|
-
for (const [key, value] of Object.entries(txtOptions)) {
|
|
3216
|
-
if (!query.has(key)) {
|
|
3217
|
-
query.set(key, value);
|
|
3218
|
-
}
|
|
3219
|
-
}
|
|
3220
|
-
if (!query.has("tls") && !query.has("ssl")) {
|
|
3221
|
-
query.set("tls", "true");
|
|
3222
|
-
}
|
|
3223
|
-
if ((url.username || url.password) && !query.has("authSource")) {
|
|
3224
|
-
query.set("authSource", "admin");
|
|
3225
|
-
}
|
|
3226
|
-
const userInfo = url.username || url.password ? `${encodeURIComponent(decodeURIComponent(url.username))}${url.password ? `:${encodeURIComponent(decodeURIComponent(url.password))}` : ""}@` : "";
|
|
3227
|
-
const path = url.pathname && url.pathname !== "/" ? url.pathname : `/${this.options.database || "test"}`;
|
|
3228
|
-
const search = query.toString();
|
|
3229
|
-
return `mongodb://${userInfo}${hosts.join(",")}${path}${search ? `?${search}` : ""}`;
|
|
3230
|
-
}
|
|
3231
|
-
connectionHints(error, message) {
|
|
3232
|
-
const AUTH_HINTS = [
|
|
3233
|
-
"\u8A8D\u8B49\u5931\u6557\uFF1A\u8ACB\u78BA\u8A8D user / password \u6B63\u78BA",
|
|
3234
|
-
"\u8ACB\u78BA\u8A8D authSource \u6307\u5411\u5B58\u653E\u8A72\u5E33\u865F\u7684\u8CC7\u6599\u5EAB\uFF08Atlas \u8207\u591A\u6578\u81EA\u67B6\u74B0\u5883\u70BA admin\uFF09"
|
|
3235
|
-
];
|
|
3236
|
-
const DNS_HINTS = [
|
|
3237
|
-
"DNS/SRV \u89E3\u6790\u5931\u6557\uFF1A\u8ACB\u78BA\u8A8D host \u70BA SRV \u7DB2\u57DF\uFF0C\u4E14 srv \u8A2D\u5B9A\u8207\u5B83\u4E00\u81F4",
|
|
3238
|
-
"\u82E5\u8A72\u4E3B\u6A5F\u4E0D\u662F SRV \u7DB2\u57DF\uFF0C\u8ACB\u95DC\u9589 srv \u4E26\u6539\u586B host \u8207 port",
|
|
3239
|
-
"\u8ACB\u78BA\u8A8D\u672C\u6A5F DNS \u6216\u7DB2\u8DEF\uFF08VPN\u3001\u516C\u53F8\u7DB2\u8DEF\uFF09\u5141\u8A31 SRV \u67E5\u8A62"
|
|
3240
|
-
];
|
|
3241
|
-
const TLS_HINTS = [
|
|
3242
|
-
"TLS \u63E1\u624B\u5931\u6557\uFF1A\u8ACB\u78BA\u8A8D tls \u6B04\u4F4D\u8A2D\u5B9A\u8207\u4F3A\u670D\u5668\u4E00\u81F4",
|
|
3243
|
-
"\u81EA\u7C3D\u6191\u8B49\u74B0\u5883\u9700\u8981\u5728\u4F3A\u670D\u5668\u7AEF\u6216\u7CFB\u7D71\u4FE1\u4EFB\u93C8\u4E2D\u5B89\u88DD CA \u6191\u8B49"
|
|
3244
|
-
];
|
|
3245
|
-
const err = error;
|
|
3246
|
-
const causeCode = String(err?.cause?.code ?? err?.code ?? "");
|
|
3247
|
-
if (err?.code === 18 || err?.codeName === "AuthenticationFailed")
|
|
3248
|
-
return AUTH_HINTS;
|
|
3249
|
-
if (["ENOTFOUND", "EAI_AGAIN"].includes(causeCode))
|
|
3250
|
-
return DNS_HINTS;
|
|
3251
|
-
if (causeCode.startsWith("ERR_TLS") || causeCode.startsWith("SELF_SIGNED"))
|
|
3252
|
-
return TLS_HINTS;
|
|
3253
|
-
if (/authentication failed|not authorized|bad auth/i.test(message))
|
|
3254
|
-
return AUTH_HINTS;
|
|
3255
|
-
if (/querySrv|getaddrinfo (ENOTFOUND|EAI_AGAIN)/i.test(message))
|
|
3256
|
-
return DNS_HINTS;
|
|
3257
|
-
if (/unable to verify the first certificate|self.signed certificate|certificate has expired|ERR_TLS/i.test(message)) {
|
|
3258
|
-
return TLS_HINTS;
|
|
3259
|
-
}
|
|
3260
|
-
return ["\u8ACB\u78BA\u8A8D MongoDB \u670D\u52D9\u6B63\u5728\u57F7\u884C", "\u8ACB\u78BA\u8A8D\u9023\u7DDA\u8A2D\u5B9A\uFF08URI \u6216 host/port\uFF09\u6B63\u78BA"];
|
|
3261
|
-
}
|
|
3262
|
-
getDatabase() {
|
|
3263
|
-
if (!this.client) {
|
|
3264
|
-
throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", []);
|
|
3265
|
-
}
|
|
3266
|
-
return this.client.db(this.options.database || undefined);
|
|
3267
|
-
}
|
|
3268
|
-
async connect() {
|
|
3269
|
-
const ClientClass = await this.resolveClientClass();
|
|
3270
|
-
const uri = await this.buildResolvedUri();
|
|
3271
|
-
try {
|
|
3272
|
-
this.client = new ClientClass(uri, { serverSelectionTimeoutMS: this.options.timeout ?? 5000 });
|
|
3273
|
-
await this.client.connect();
|
|
3274
|
-
} catch (err) {
|
|
3275
|
-
const message = err.message ?? "Unknown error";
|
|
3276
|
-
const code = message.includes("ECONNREFUSED") ? "ECONNREFUSED" : message.includes("ETIMEDOUT") ? "ETIMEDOUT" : "UNKNOWN";
|
|
3277
|
-
throw new ConnectionError(code, `MongoDB \u9023\u7DDA\u5931\u6557: ${message}`, this.connectionHints(err, message));
|
|
2197
|
+
const globstar = segments.find((seg) => seg.includes("**"));
|
|
2198
|
+
if (globstar !== undefined) {
|
|
2199
|
+
rejected.push({
|
|
2200
|
+
raw: entry,
|
|
2201
|
+
reason: "`**` matches one segment, not a subtree; write `*` or a longer path"
|
|
2202
|
+
});
|
|
2203
|
+
continue;
|
|
3278
2204
|
}
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
this.client = null;
|
|
2205
|
+
const dead = segments.map((seg) => globNeverMatches(seg)).find((r) => r !== null);
|
|
2206
|
+
if (dead !== undefined && dead !== null) {
|
|
2207
|
+
rejected.push({ raw: entry, reason: dead });
|
|
2208
|
+
continue;
|
|
3284
2209
|
}
|
|
2210
|
+
const wildcardTail = segments.length > 1 && segments[segments.length - 1] === "*";
|
|
2211
|
+
const literal = wildcardTail ? segments.slice(0, -1) : segments;
|
|
2212
|
+
patterns.push({
|
|
2213
|
+
raw: entry,
|
|
2214
|
+
segments: literal,
|
|
2215
|
+
wildcardTail
|
|
2216
|
+
});
|
|
3285
2217
|
}
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
assertNoMongoServerSideScript(parsed);
|
|
3297
|
-
const limit = options?.limit;
|
|
3298
|
-
let docs;
|
|
3299
|
-
if (Array.isArray(parsed)) {
|
|
3300
|
-
const stages = parsed;
|
|
3301
|
-
const alreadyHasLimit = stages.some((stage) => stage !== null && typeof stage === "object" && ("$limit" in stage));
|
|
3302
|
-
const limitedStages = typeof limit === "number" && limit > 0 && !alreadyHasLimit ? [...stages, { $limit: limit }] : stages;
|
|
3303
|
-
const finalStages = options?.projection ? [...limitedStages, { $project: options.projection }] : limitedStages;
|
|
3304
|
-
docs = await collection.aggregate(finalStages).toArray();
|
|
2218
|
+
return { patterns, rejected };
|
|
2219
|
+
}
|
|
2220
|
+
function matchAny(path, patterns) {
|
|
2221
|
+
if (patterns.length === 0)
|
|
2222
|
+
return false;
|
|
2223
|
+
const pathSegments = path.split(".");
|
|
2224
|
+
for (const pat of patterns) {
|
|
2225
|
+
if (pat.wildcardTail) {
|
|
2226
|
+
if (pathSegments.length < pat.segments.length)
|
|
2227
|
+
continue;
|
|
3305
2228
|
} else {
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
docs = await cursor.limit(cap2).toArray();
|
|
2229
|
+
if (pat.segments.length !== pathSegments.length)
|
|
2230
|
+
continue;
|
|
3309
2231
|
}
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
const results = await Promise.all(colls.map(async (col) => {
|
|
3316
|
-
try {
|
|
3317
|
-
const count = await db.collection(col.name).estimatedDocumentCount();
|
|
3318
|
-
return { name: col.name, documentCount: count };
|
|
3319
|
-
} catch {
|
|
3320
|
-
return { name: col.name };
|
|
3321
|
-
}
|
|
3322
|
-
}));
|
|
3323
|
-
return results;
|
|
3324
|
-
}
|
|
3325
|
-
async listTables() {
|
|
3326
|
-
const collections = await this.listCollections();
|
|
3327
|
-
return collections.map((c) => ({
|
|
3328
|
-
name: c.name,
|
|
3329
|
-
estimatedRowCount: c.documentCount,
|
|
3330
|
-
columns: []
|
|
3331
|
-
}));
|
|
3332
|
-
}
|
|
3333
|
-
async getTableSchema(collectionName, options) {
|
|
3334
|
-
const db = this.getDatabase();
|
|
3335
|
-
const collection = db.collection(collectionName);
|
|
3336
|
-
const estimatedRowCount = await collection.estimatedDocumentCount();
|
|
3337
|
-
const requested = options?.sampleSize;
|
|
3338
|
-
const desired = typeof requested === "number" && requested >= 1 ? requested : 100;
|
|
3339
|
-
const sampleSize = Math.min(desired, 1000);
|
|
3340
|
-
const method = options?.sampleMethod ?? "random";
|
|
3341
|
-
let samples = [];
|
|
3342
|
-
if (method === "random") {
|
|
3343
|
-
try {
|
|
3344
|
-
samples = await collection.aggregate([{ $sample: { size: sampleSize } }]).toArray();
|
|
3345
|
-
} catch (err) {
|
|
3346
|
-
console.error(`[mongo] $sample failed (${err.message}); falling back to natural order.`);
|
|
3347
|
-
samples = await collection.find().limit(sampleSize).toArray();
|
|
2232
|
+
let ok = true;
|
|
2233
|
+
for (let i = 0;i < pat.segments.length; i++) {
|
|
2234
|
+
if (!globMatches(pat.segments[i], pathSegments[i])) {
|
|
2235
|
+
ok = false;
|
|
2236
|
+
break;
|
|
3348
2237
|
}
|
|
3349
|
-
} else {
|
|
3350
|
-
samples = await collection.find().limit(sampleSize).toArray();
|
|
3351
|
-
}
|
|
3352
|
-
const accumulator = new Map;
|
|
3353
|
-
for (const doc of samples)
|
|
3354
|
-
accumulateDocPaths(doc, "", accumulator);
|
|
3355
|
-
const denom = Math.max(samples.length, 1);
|
|
3356
|
-
const columns = Array.from(accumulator.entries()).map(([name, info]) => ({
|
|
3357
|
-
name,
|
|
3358
|
-
type: Array.from(info.types).sort().join(" | "),
|
|
3359
|
-
nullable: info.types.has("null") || info.count < denom,
|
|
3360
|
-
presence: info.count / denom
|
|
3361
|
-
}));
|
|
3362
|
-
return {
|
|
3363
|
-
name: collectionName,
|
|
3364
|
-
columns,
|
|
3365
|
-
estimatedRowCount,
|
|
3366
|
-
tableType: "table"
|
|
3367
|
-
};
|
|
3368
|
-
}
|
|
3369
|
-
async testConnection() {
|
|
3370
|
-
if (!this.client)
|
|
3371
|
-
return false;
|
|
3372
|
-
await this.getDatabase().command({ ping: 1 });
|
|
3373
|
-
return true;
|
|
3374
|
-
}
|
|
3375
|
-
async getServerVersion() {
|
|
3376
|
-
const info = await this.getDatabase().admin().serverInfo();
|
|
3377
|
-
return info.version ?? "unknown";
|
|
3378
|
-
}
|
|
3379
|
-
async insert(collection, data) {
|
|
3380
|
-
const db = this.getDatabase();
|
|
3381
|
-
const result = await db.collection(collection).insertOne(data);
|
|
3382
|
-
return {
|
|
3383
|
-
rows: [],
|
|
3384
|
-
affectedRows: result.acknowledged ? 1 : 0,
|
|
3385
|
-
lastInsertId: result.insertedId.toString()
|
|
3386
|
-
};
|
|
3387
|
-
}
|
|
3388
|
-
async update(collection, filter, update) {
|
|
3389
|
-
const db = this.getDatabase();
|
|
3390
|
-
assertNoMongoServerSideScript(filter);
|
|
3391
|
-
assertNoMongoServerSideScript(update);
|
|
3392
|
-
const result = await db.collection(collection).updateMany(filter, update);
|
|
3393
|
-
return {
|
|
3394
|
-
rows: [],
|
|
3395
|
-
affectedRows: result.modifiedCount
|
|
3396
|
-
};
|
|
3397
|
-
}
|
|
3398
|
-
async delete(collection, filter) {
|
|
3399
|
-
const db = this.getDatabase();
|
|
3400
|
-
assertNoMongoServerSideScript(filter);
|
|
3401
|
-
const result = await db.collection(collection).deleteMany(filter);
|
|
3402
|
-
return {
|
|
3403
|
-
rows: [],
|
|
3404
|
-
affectedRows: result.deletedCount
|
|
3405
|
-
};
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
function detectMongoType(v) {
|
|
3409
|
-
if (v === null || v === undefined)
|
|
3410
|
-
return "null";
|
|
3411
|
-
if (Array.isArray(v))
|
|
3412
|
-
return "array";
|
|
3413
|
-
if (v instanceof Date)
|
|
3414
|
-
return "Date";
|
|
3415
|
-
if (typeof v === "object") {
|
|
3416
|
-
const ctor = v.constructor?.name;
|
|
3417
|
-
if (ctor === "ObjectId")
|
|
3418
|
-
return "ObjectId";
|
|
3419
|
-
if (ctor === "Binary")
|
|
3420
|
-
return "Binary";
|
|
3421
|
-
if (ctor === "Decimal128")
|
|
3422
|
-
return "Decimal128";
|
|
3423
|
-
return "object";
|
|
3424
|
-
}
|
|
3425
|
-
if (typeof v === "string")
|
|
3426
|
-
return "string";
|
|
3427
|
-
if (typeof v === "number")
|
|
3428
|
-
return "number";
|
|
3429
|
-
if (typeof v === "boolean")
|
|
3430
|
-
return "boolean";
|
|
3431
|
-
return "string";
|
|
3432
|
-
}
|
|
3433
|
-
function accumulateDocPaths(doc, prefix, acc) {
|
|
3434
|
-
for (const [k, v] of Object.entries(doc)) {
|
|
3435
|
-
const path = prefix === "" ? k : `${prefix}.${k}`;
|
|
3436
|
-
const t2 = detectMongoType(v);
|
|
3437
|
-
const slot = acc.get(path) ?? { types: new Set, count: 0 };
|
|
3438
|
-
slot.types.add(t2);
|
|
3439
|
-
slot.count += 1;
|
|
3440
|
-
acc.set(path, slot);
|
|
3441
|
-
if (t2 === "object" && v && typeof v === "object") {
|
|
3442
|
-
accumulateDocPaths(v, path, acc);
|
|
3443
2238
|
}
|
|
2239
|
+
if (ok)
|
|
2240
|
+
return true;
|
|
3444
2241
|
}
|
|
2242
|
+
return false;
|
|
3445
2243
|
}
|
|
3446
2244
|
|
|
2245
|
+
// src/core/mongo/request-fields.ts
|
|
2246
|
+
var WHOLE_DOCUMENT_VARIABLES = new Set(["ROOT", "CURRENT"]);
|
|
2247
|
+
var RESHAPES_DOCUMENT = new Set(["$objectToArray", "$replaceRoot", "$replaceWith"]);
|
|
2248
|
+
|
|
3447
2249
|
// src/adapters/redis/command-metadata.ts
|
|
3448
2250
|
var tbl = (s) => s;
|
|
3449
2251
|
var REDIS_COMMAND_TABLE = Object.freeze({
|
|
3450
2252
|
PING: tbl({
|
|
3451
2253
|
readOnly: true,
|
|
3452
2254
|
sizeGuard: { kind: "unbounded" },
|
|
3453
|
-
keyArity: { kind: "no-key" }
|
|
3454
|
-
permissionTier: "query-only"
|
|
2255
|
+
keyArity: { kind: "no-key" }
|
|
3455
2256
|
}),
|
|
3456
2257
|
INFO: tbl({
|
|
3457
2258
|
readOnly: true,
|
|
3458
2259
|
sizeGuard: { kind: "unbounded" },
|
|
3459
|
-
keyArity: { kind: "no-key" }
|
|
3460
|
-
permissionTier: "query-only"
|
|
2260
|
+
keyArity: { kind: "no-key" }
|
|
3461
2261
|
}),
|
|
3462
2262
|
DBSIZE: tbl({
|
|
3463
2263
|
readOnly: true,
|
|
3464
2264
|
sizeGuard: { kind: "unbounded" },
|
|
3465
|
-
keyArity: { kind: "no-key" }
|
|
3466
|
-
permissionTier: "query-only"
|
|
2265
|
+
keyArity: { kind: "no-key" }
|
|
3467
2266
|
}),
|
|
3468
2267
|
TYPE: tbl({
|
|
3469
2268
|
readOnly: true,
|
|
3470
2269
|
sizeGuard: { kind: "unbounded" },
|
|
3471
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3472
|
-
permissionTier: "query-only"
|
|
2270
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3473
2271
|
}),
|
|
3474
2272
|
EXISTS: tbl({
|
|
3475
2273
|
readOnly: true,
|
|
3476
2274
|
sizeGuard: { kind: "unbounded" },
|
|
3477
|
-
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3478
|
-
permissionTier: "query-only"
|
|
2275
|
+
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3479
2276
|
}),
|
|
3480
2277
|
TTL: tbl({
|
|
3481
2278
|
readOnly: true,
|
|
3482
2279
|
sizeGuard: { kind: "unbounded" },
|
|
3483
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3484
|
-
permissionTier: "query-only"
|
|
2280
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3485
2281
|
}),
|
|
3486
2282
|
GET: tbl({
|
|
3487
2283
|
readOnly: true,
|
|
3488
2284
|
sizeGuard: { kind: "unbounded" },
|
|
3489
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3490
|
-
permissionTier: "query-only"
|
|
2285
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3491
2286
|
}),
|
|
3492
2287
|
MGET: tbl({
|
|
3493
2288
|
readOnly: true,
|
|
3494
2289
|
sizeGuard: { kind: "unbounded" },
|
|
3495
|
-
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3496
|
-
permissionTier: "query-only"
|
|
2290
|
+
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3497
2291
|
}),
|
|
3498
2292
|
STRLEN: tbl({
|
|
3499
2293
|
readOnly: true,
|
|
3500
2294
|
sizeGuard: { kind: "unbounded" },
|
|
3501
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3502
|
-
permissionTier: "query-only"
|
|
2295
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3503
2296
|
}),
|
|
3504
2297
|
HGET: tbl({
|
|
3505
2298
|
readOnly: true,
|
|
3506
2299
|
sizeGuard: { kind: "unbounded" },
|
|
3507
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3508
|
-
permissionTier: "query-only"
|
|
2300
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3509
2301
|
}),
|
|
3510
2302
|
HEXISTS: tbl({
|
|
3511
2303
|
readOnly: true,
|
|
3512
2304
|
sizeGuard: { kind: "unbounded" },
|
|
3513
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3514
|
-
permissionTier: "query-only"
|
|
2305
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3515
2306
|
}),
|
|
3516
2307
|
HLEN: tbl({
|
|
3517
2308
|
readOnly: true,
|
|
3518
2309
|
sizeGuard: { kind: "unbounded" },
|
|
3519
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3520
|
-
permissionTier: "query-only"
|
|
2310
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3521
2311
|
}),
|
|
3522
2312
|
HMGET: tbl({
|
|
3523
2313
|
readOnly: true,
|
|
3524
2314
|
sizeGuard: { kind: "unbounded" },
|
|
3525
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3526
|
-
permissionTier: "query-only"
|
|
2315
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3527
2316
|
}),
|
|
3528
2317
|
LLEN: tbl({
|
|
3529
2318
|
readOnly: true,
|
|
3530
2319
|
sizeGuard: { kind: "unbounded" },
|
|
3531
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3532
|
-
permissionTier: "query-only"
|
|
2320
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3533
2321
|
}),
|
|
3534
2322
|
LINDEX: tbl({
|
|
3535
2323
|
readOnly: true,
|
|
3536
2324
|
sizeGuard: { kind: "unbounded" },
|
|
3537
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3538
|
-
permissionTier: "query-only"
|
|
2325
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3539
2326
|
}),
|
|
3540
2327
|
SCARD: tbl({
|
|
3541
2328
|
readOnly: true,
|
|
3542
2329
|
sizeGuard: { kind: "unbounded" },
|
|
3543
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3544
|
-
permissionTier: "query-only"
|
|
2330
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3545
2331
|
}),
|
|
3546
2332
|
SISMEMBER: tbl({
|
|
3547
2333
|
readOnly: true,
|
|
3548
2334
|
sizeGuard: { kind: "unbounded" },
|
|
3549
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3550
|
-
permissionTier: "query-only"
|
|
2335
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3551
2336
|
}),
|
|
3552
2337
|
ZCARD: tbl({
|
|
3553
2338
|
readOnly: true,
|
|
3554
2339
|
sizeGuard: { kind: "unbounded" },
|
|
3555
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3556
|
-
permissionTier: "query-only"
|
|
2340
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3557
2341
|
}),
|
|
3558
2342
|
ZSCORE: tbl({
|
|
3559
2343
|
readOnly: true,
|
|
3560
2344
|
sizeGuard: { kind: "unbounded" },
|
|
3561
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3562
|
-
permissionTier: "query-only"
|
|
2345
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3563
2346
|
}),
|
|
3564
2347
|
XLEN: tbl({
|
|
3565
2348
|
readOnly: true,
|
|
3566
2349
|
sizeGuard: { kind: "unbounded" },
|
|
3567
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3568
|
-
permissionTier: "query-only"
|
|
2350
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3569
2351
|
}),
|
|
3570
2352
|
SCAN: tbl({
|
|
3571
2353
|
readOnly: true,
|
|
3572
2354
|
sizeGuard: { kind: "rewrite-count", argIndex: 0 },
|
|
3573
|
-
keyArity: { kind: "
|
|
3574
|
-
permissionTier: "query-only"
|
|
2355
|
+
keyArity: { kind: "pattern-after-token", token: "MATCH" }
|
|
3575
2356
|
}),
|
|
3576
2357
|
HSCAN: tbl({
|
|
3577
2358
|
readOnly: true,
|
|
3578
2359
|
sizeGuard: { kind: "rewrite-count", argIndex: 1 },
|
|
3579
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3580
|
-
permissionTier: "query-only"
|
|
2360
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3581
2361
|
}),
|
|
3582
2362
|
SSCAN: tbl({
|
|
3583
2363
|
readOnly: true,
|
|
3584
2364
|
sizeGuard: { kind: "rewrite-count", argIndex: 1 },
|
|
3585
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3586
|
-
permissionTier: "query-only"
|
|
2365
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3587
2366
|
}),
|
|
3588
2367
|
ZSCAN: tbl({
|
|
3589
2368
|
readOnly: true,
|
|
3590
2369
|
sizeGuard: { kind: "rewrite-count", argIndex: 1 },
|
|
3591
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3592
|
-
permissionTier: "query-only"
|
|
2370
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3593
2371
|
}),
|
|
3594
2372
|
LRANGE: tbl({
|
|
3595
2373
|
readOnly: true,
|
|
3596
2374
|
sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
|
|
3597
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3598
|
-
permissionTier: "query-only"
|
|
2375
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3599
2376
|
}),
|
|
3600
2377
|
ZRANGE: tbl({
|
|
3601
2378
|
readOnly: true,
|
|
3602
2379
|
sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
|
|
3603
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3604
|
-
permissionTier: "query-only"
|
|
2380
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3605
2381
|
}),
|
|
3606
2382
|
ZREVRANGE: tbl({
|
|
3607
2383
|
readOnly: true,
|
|
3608
2384
|
sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
|
|
3609
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3610
|
-
permissionTier: "query-only"
|
|
2385
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3611
2386
|
}),
|
|
3612
2387
|
ZRANGEBYSCORE: tbl({
|
|
3613
2388
|
readOnly: true,
|
|
3614
2389
|
sizeGuard: { kind: "rewrite-limit", argIndex: 3 },
|
|
3615
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3616
|
-
permissionTier: "query-only"
|
|
2390
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3617
2391
|
}),
|
|
3618
2392
|
HGETALL: tbl({
|
|
3619
2393
|
readOnly: true,
|
|
3620
2394
|
sizeGuard: { kind: "truncate" },
|
|
3621
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3622
|
-
permissionTier: "query-only"
|
|
2395
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3623
2396
|
}),
|
|
3624
2397
|
HKEYS: tbl({
|
|
3625
2398
|
readOnly: true,
|
|
3626
2399
|
sizeGuard: { kind: "truncate" },
|
|
3627
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3628
|
-
permissionTier: "query-only"
|
|
2400
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3629
2401
|
}),
|
|
3630
2402
|
HVALS: tbl({
|
|
3631
2403
|
readOnly: true,
|
|
3632
2404
|
sizeGuard: { kind: "truncate" },
|
|
3633
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3634
|
-
permissionTier: "query-only"
|
|
2405
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3635
2406
|
}),
|
|
3636
2407
|
SMEMBERS: tbl({
|
|
3637
2408
|
readOnly: true,
|
|
3638
2409
|
sizeGuard: { kind: "truncate" },
|
|
3639
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3640
|
-
permissionTier: "query-only"
|
|
2410
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3641
2411
|
}),
|
|
3642
2412
|
KEYS: tbl({
|
|
3643
2413
|
readOnly: true,
|
|
3644
2414
|
sizeGuard: { kind: "truncate" },
|
|
3645
|
-
keyArity: { kind: "pattern", argIndex: 0 }
|
|
3646
|
-
permissionTier: "query-only"
|
|
2415
|
+
keyArity: { kind: "pattern", argIndex: 0 }
|
|
3647
2416
|
}),
|
|
3648
2417
|
SET: tbl({
|
|
3649
2418
|
readOnly: false,
|
|
3650
2419
|
sizeGuard: { kind: "unbounded" },
|
|
3651
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3652
|
-
permissionTier: "read-write"
|
|
2420
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3653
2421
|
}),
|
|
3654
2422
|
SETEX: tbl({
|
|
3655
2423
|
readOnly: false,
|
|
3656
2424
|
sizeGuard: { kind: "unbounded" },
|
|
3657
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3658
|
-
permissionTier: "read-write"
|
|
2425
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3659
2426
|
}),
|
|
3660
2427
|
MSET: tbl({
|
|
3661
2428
|
readOnly: false,
|
|
3662
2429
|
sizeGuard: { kind: "unbounded" },
|
|
3663
|
-
keyArity: { kind: "multi-variable", startIndex: 0, step: 2 }
|
|
3664
|
-
permissionTier: "read-write"
|
|
2430
|
+
keyArity: { kind: "multi-variable", startIndex: 0, step: 2 }
|
|
3665
2431
|
}),
|
|
3666
2432
|
HSET: tbl({
|
|
3667
2433
|
readOnly: false,
|
|
3668
2434
|
sizeGuard: { kind: "unbounded" },
|
|
3669
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3670
|
-
permissionTier: "read-write"
|
|
2435
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3671
2436
|
}),
|
|
3672
2437
|
HMSET: tbl({
|
|
3673
2438
|
readOnly: false,
|
|
3674
2439
|
sizeGuard: { kind: "unbounded" },
|
|
3675
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3676
|
-
permissionTier: "read-write"
|
|
2440
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3677
2441
|
}),
|
|
3678
2442
|
HDEL: tbl({
|
|
3679
2443
|
readOnly: false,
|
|
3680
2444
|
sizeGuard: { kind: "unbounded" },
|
|
3681
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3682
|
-
permissionTier: "read-write"
|
|
2445
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3683
2446
|
}),
|
|
3684
2447
|
RPUSH: tbl({
|
|
3685
2448
|
readOnly: false,
|
|
3686
2449
|
sizeGuard: { kind: "unbounded" },
|
|
3687
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3688
|
-
permissionTier: "read-write"
|
|
2450
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3689
2451
|
}),
|
|
3690
2452
|
LPUSH: tbl({
|
|
3691
2453
|
readOnly: false,
|
|
3692
2454
|
sizeGuard: { kind: "unbounded" },
|
|
3693
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3694
|
-
permissionTier: "read-write"
|
|
2455
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3695
2456
|
}),
|
|
3696
2457
|
LREM: tbl({
|
|
3697
2458
|
readOnly: false,
|
|
3698
2459
|
sizeGuard: { kind: "unbounded" },
|
|
3699
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3700
|
-
permissionTier: "read-write"
|
|
2460
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3701
2461
|
}),
|
|
3702
2462
|
SADD: tbl({
|
|
3703
2463
|
readOnly: false,
|
|
3704
2464
|
sizeGuard: { kind: "unbounded" },
|
|
3705
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3706
|
-
permissionTier: "read-write"
|
|
2465
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3707
2466
|
}),
|
|
3708
2467
|
SREM: tbl({
|
|
3709
2468
|
readOnly: false,
|
|
3710
2469
|
sizeGuard: { kind: "unbounded" },
|
|
3711
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3712
|
-
permissionTier: "read-write"
|
|
2470
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3713
2471
|
}),
|
|
3714
2472
|
ZADD: tbl({
|
|
3715
2473
|
readOnly: false,
|
|
3716
2474
|
sizeGuard: { kind: "unbounded" },
|
|
3717
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3718
|
-
permissionTier: "read-write"
|
|
2475
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3719
2476
|
}),
|
|
3720
2477
|
ZREM: tbl({
|
|
3721
2478
|
readOnly: false,
|
|
3722
2479
|
sizeGuard: { kind: "unbounded" },
|
|
3723
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3724
|
-
permissionTier: "read-write"
|
|
2480
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3725
2481
|
}),
|
|
3726
2482
|
DEL: tbl({
|
|
3727
2483
|
readOnly: false,
|
|
3728
2484
|
sizeGuard: { kind: "unbounded" },
|
|
3729
|
-
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3730
|
-
permissionTier: "read-write"
|
|
2485
|
+
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3731
2486
|
}),
|
|
3732
2487
|
UNLINK: tbl({
|
|
3733
2488
|
readOnly: false,
|
|
3734
2489
|
sizeGuard: { kind: "unbounded" },
|
|
3735
|
-
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3736
|
-
permissionTier: "read-write"
|
|
2490
|
+
keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
|
|
3737
2491
|
}),
|
|
3738
2492
|
EXPIRE: tbl({
|
|
3739
2493
|
readOnly: false,
|
|
3740
2494
|
sizeGuard: { kind: "unbounded" },
|
|
3741
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3742
|
-
permissionTier: "read-write"
|
|
2495
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3743
2496
|
}),
|
|
3744
2497
|
PERSIST: tbl({
|
|
3745
2498
|
readOnly: false,
|
|
3746
2499
|
sizeGuard: { kind: "unbounded" },
|
|
3747
|
-
keyArity: { kind: "single", argIndex: 0 }
|
|
3748
|
-
permissionTier: "read-write"
|
|
2500
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
3749
2501
|
}),
|
|
3750
2502
|
RENAME: tbl({
|
|
3751
2503
|
readOnly: false,
|
|
3752
2504
|
sizeGuard: { kind: "unbounded" },
|
|
3753
|
-
keyArity: { kind: "multi-fixed", argIndices: [0, 1] }
|
|
3754
|
-
permissionTier: "read-write"
|
|
2505
|
+
keyArity: { kind: "multi-fixed", argIndices: [0, 1] }
|
|
3755
2506
|
}),
|
|
3756
2507
|
FLUSHDB: tbl({
|
|
3757
2508
|
readOnly: false,
|
|
3758
2509
|
sizeGuard: { kind: "reject", reason: "FLUSHDB destroys data" },
|
|
3759
|
-
keyArity: { kind: "no-key" }
|
|
3760
|
-
permissionTier: "admin"
|
|
2510
|
+
keyArity: { kind: "no-key" }
|
|
3761
2511
|
}),
|
|
3762
2512
|
FLUSHALL: tbl({
|
|
3763
2513
|
readOnly: false,
|
|
3764
2514
|
sizeGuard: { kind: "reject", reason: "FLUSHALL destroys data" },
|
|
3765
|
-
keyArity: { kind: "no-key" }
|
|
3766
|
-
permissionTier: "admin"
|
|
2515
|
+
keyArity: { kind: "no-key" }
|
|
3767
2516
|
}),
|
|
3768
2517
|
SHUTDOWN: tbl({
|
|
3769
2518
|
readOnly: false,
|
|
3770
2519
|
sizeGuard: { kind: "reject", reason: "SHUTDOWN stops the server" },
|
|
3771
|
-
keyArity: { kind: "no-key" }
|
|
3772
|
-
permissionTier: "admin"
|
|
2520
|
+
keyArity: { kind: "no-key" }
|
|
3773
2521
|
}),
|
|
3774
2522
|
DEBUG: tbl({
|
|
3775
2523
|
readOnly: false,
|
|
3776
2524
|
sizeGuard: { kind: "reject", reason: "DEBUG is unsafe" },
|
|
3777
|
-
keyArity: { kind: "no-key" }
|
|
3778
|
-
permissionTier: "admin"
|
|
2525
|
+
keyArity: { kind: "no-key" }
|
|
3779
2526
|
}),
|
|
3780
|
-
|
|
2527
|
+
PTTL: tbl({
|
|
2528
|
+
readOnly: true,
|
|
2529
|
+
sizeGuard: { kind: "unbounded" },
|
|
2530
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2531
|
+
}),
|
|
2532
|
+
SETNX: tbl({
|
|
3781
2533
|
readOnly: false,
|
|
3782
|
-
sizeGuard: { kind: "
|
|
3783
|
-
keyArity: { kind: "
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
}
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
}
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
}
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
}
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
}
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
}
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
out += "\\" + c;
|
|
3937
|
-
} else {
|
|
3938
|
-
out += c;
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
out += "$";
|
|
3942
|
-
return new RegExp(out);
|
|
3943
|
-
}
|
|
3944
|
-
function findClassEnd(glob, open) {
|
|
3945
|
-
for (let i = open + 1;i < glob.length; i++) {
|
|
3946
|
-
if (glob[i] === "\\") {
|
|
3947
|
-
i++;
|
|
3948
|
-
continue;
|
|
3949
|
-
}
|
|
3950
|
-
if (glob[i] === "]")
|
|
3951
|
-
return i;
|
|
3952
|
-
}
|
|
3953
|
-
return -1;
|
|
3954
|
-
}
|
|
3955
|
-
function isValidCharacterClass(body) {
|
|
3956
|
-
try {
|
|
3957
|
-
new RegExp(body);
|
|
3958
|
-
return true;
|
|
3959
|
-
} catch {
|
|
3960
|
-
return false;
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
2534
|
+
sizeGuard: { kind: "unbounded" },
|
|
2535
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2536
|
+
}),
|
|
2537
|
+
PSETEX: tbl({
|
|
2538
|
+
readOnly: false,
|
|
2539
|
+
sizeGuard: { kind: "unbounded" },
|
|
2540
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2541
|
+
}),
|
|
2542
|
+
APPEND: tbl({
|
|
2543
|
+
readOnly: false,
|
|
2544
|
+
sizeGuard: { kind: "unbounded" },
|
|
2545
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2546
|
+
}),
|
|
2547
|
+
INCR: tbl({
|
|
2548
|
+
readOnly: false,
|
|
2549
|
+
sizeGuard: { kind: "unbounded" },
|
|
2550
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2551
|
+
}),
|
|
2552
|
+
INCRBY: tbl({
|
|
2553
|
+
readOnly: false,
|
|
2554
|
+
sizeGuard: { kind: "unbounded" },
|
|
2555
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2556
|
+
}),
|
|
2557
|
+
DECR: tbl({
|
|
2558
|
+
readOnly: false,
|
|
2559
|
+
sizeGuard: { kind: "unbounded" },
|
|
2560
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2561
|
+
}),
|
|
2562
|
+
DECRBY: tbl({
|
|
2563
|
+
readOnly: false,
|
|
2564
|
+
sizeGuard: { kind: "unbounded" },
|
|
2565
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2566
|
+
}),
|
|
2567
|
+
HSETNX: tbl({
|
|
2568
|
+
readOnly: false,
|
|
2569
|
+
sizeGuard: { kind: "unbounded" },
|
|
2570
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2571
|
+
}),
|
|
2572
|
+
HINCRBY: tbl({
|
|
2573
|
+
readOnly: false,
|
|
2574
|
+
sizeGuard: { kind: "unbounded" },
|
|
2575
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2576
|
+
}),
|
|
2577
|
+
LPOP: tbl({
|
|
2578
|
+
readOnly: false,
|
|
2579
|
+
sizeGuard: { kind: "unbounded" },
|
|
2580
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2581
|
+
}),
|
|
2582
|
+
RPOP: tbl({
|
|
2583
|
+
readOnly: false,
|
|
2584
|
+
sizeGuard: { kind: "unbounded" },
|
|
2585
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2586
|
+
}),
|
|
2587
|
+
LSET: tbl({
|
|
2588
|
+
readOnly: false,
|
|
2589
|
+
sizeGuard: { kind: "unbounded" },
|
|
2590
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2591
|
+
}),
|
|
2592
|
+
XADD: tbl({
|
|
2593
|
+
readOnly: false,
|
|
2594
|
+
sizeGuard: { kind: "unbounded" },
|
|
2595
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2596
|
+
}),
|
|
2597
|
+
XDEL: tbl({
|
|
2598
|
+
readOnly: false,
|
|
2599
|
+
sizeGuard: { kind: "unbounded" },
|
|
2600
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2601
|
+
}),
|
|
2602
|
+
XRANGE: tbl({
|
|
2603
|
+
readOnly: true,
|
|
2604
|
+
sizeGuard: { kind: "unbounded" },
|
|
2605
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2606
|
+
}),
|
|
2607
|
+
XREVRANGE: tbl({
|
|
2608
|
+
readOnly: true,
|
|
2609
|
+
sizeGuard: { kind: "unbounded" },
|
|
2610
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2611
|
+
}),
|
|
2612
|
+
EXPIREAT: tbl({
|
|
2613
|
+
readOnly: false,
|
|
2614
|
+
sizeGuard: { kind: "unbounded" },
|
|
2615
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2616
|
+
}),
|
|
2617
|
+
PEXPIRE: tbl({
|
|
2618
|
+
readOnly: false,
|
|
2619
|
+
sizeGuard: { kind: "unbounded" },
|
|
2620
|
+
keyArity: { kind: "single", argIndex: 0 }
|
|
2621
|
+
}),
|
|
2622
|
+
MSETNX: tbl({
|
|
2623
|
+
readOnly: false,
|
|
2624
|
+
sizeGuard: { kind: "unbounded" },
|
|
2625
|
+
keyArity: { kind: "multi-variable", startIndex: 0, step: 2 }
|
|
2626
|
+
}),
|
|
2627
|
+
XREAD: tbl({
|
|
2628
|
+
readOnly: true,
|
|
2629
|
+
sizeGuard: { kind: "unbounded" },
|
|
2630
|
+
keyArity: { kind: "after-token", token: "STREAMS" }
|
|
2631
|
+
}),
|
|
2632
|
+
ECHO: tbl({
|
|
2633
|
+
readOnly: true,
|
|
2634
|
+
sizeGuard: { kind: "unbounded" },
|
|
2635
|
+
keyArity: { kind: "no-key" }
|
|
2636
|
+
}),
|
|
2637
|
+
CONFIG: tbl({
|
|
2638
|
+
readOnly: false,
|
|
2639
|
+
sizeGuard: { kind: "unbounded" },
|
|
2640
|
+
keyArity: { kind: "no-key" }
|
|
2641
|
+
}),
|
|
2642
|
+
CLIENT: tbl({
|
|
2643
|
+
readOnly: false,
|
|
2644
|
+
sizeGuard: { kind: "unbounded" },
|
|
2645
|
+
keyArity: { kind: "no-key" }
|
|
2646
|
+
}),
|
|
2647
|
+
MONITOR: tbl({
|
|
2648
|
+
readOnly: false,
|
|
2649
|
+
sizeGuard: { kind: "unbounded" },
|
|
2650
|
+
keyArity: { kind: "no-key" }
|
|
2651
|
+
}),
|
|
2652
|
+
SAVE: tbl({
|
|
2653
|
+
readOnly: false,
|
|
2654
|
+
sizeGuard: { kind: "unbounded" },
|
|
2655
|
+
keyArity: { kind: "no-key" }
|
|
2656
|
+
}),
|
|
2657
|
+
BGSAVE: tbl({
|
|
2658
|
+
readOnly: false,
|
|
2659
|
+
sizeGuard: { kind: "unbounded" },
|
|
2660
|
+
keyArity: { kind: "no-key" }
|
|
2661
|
+
}),
|
|
2662
|
+
BGREWRITEAOF: tbl({
|
|
2663
|
+
readOnly: false,
|
|
2664
|
+
sizeGuard: { kind: "unbounded" },
|
|
2665
|
+
keyArity: { kind: "no-key" }
|
|
2666
|
+
}),
|
|
2667
|
+
REPLICAOF: tbl({
|
|
2668
|
+
readOnly: false,
|
|
2669
|
+
sizeGuard: { kind: "unbounded" },
|
|
2670
|
+
keyArity: { kind: "no-key" }
|
|
2671
|
+
}),
|
|
2672
|
+
SLAVEOF: tbl({
|
|
2673
|
+
readOnly: false,
|
|
2674
|
+
sizeGuard: { kind: "unbounded" },
|
|
2675
|
+
keyArity: { kind: "no-key" }
|
|
2676
|
+
}),
|
|
2677
|
+
ACL: tbl({
|
|
2678
|
+
readOnly: false,
|
|
2679
|
+
sizeGuard: { kind: "unbounded" },
|
|
2680
|
+
keyArity: { kind: "no-key" }
|
|
2681
|
+
}),
|
|
2682
|
+
"CONFIG SET": tbl({
|
|
2683
|
+
readOnly: false,
|
|
2684
|
+
sizeGuard: { kind: "reject", reason: "CONFIG SET mutates server state" },
|
|
2685
|
+
keyArity: { kind: "no-key" }
|
|
2686
|
+
})
|
|
2687
|
+
});
|
|
3963
2688
|
|
|
3964
|
-
// src/adapters/redis/
|
|
3965
|
-
|
|
3966
|
-
const ra = globToRegex(a);
|
|
3967
|
-
const rb = globToRegex(b);
|
|
3968
|
-
const probeA = sampleFromGlob(a);
|
|
3969
|
-
const probeB = sampleFromGlob(b);
|
|
3970
|
-
if (probeA !== null && rb.test(probeA))
|
|
3971
|
-
return true;
|
|
3972
|
-
if (probeB !== null && ra.test(probeB))
|
|
3973
|
-
return true;
|
|
3974
|
-
return false;
|
|
3975
|
-
}
|
|
3976
|
-
function sampleFromGlob(glob) {
|
|
3977
|
-
let s = "";
|
|
3978
|
-
for (let i = 0;i < glob.length; i++) {
|
|
3979
|
-
const c = glob[i];
|
|
3980
|
-
if (c === "*")
|
|
3981
|
-
s += "__star__";
|
|
3982
|
-
else if (c === "?")
|
|
3983
|
-
s += "x";
|
|
3984
|
-
else if (c === "[") {
|
|
3985
|
-
const end = glob.indexOf("]", i);
|
|
3986
|
-
if (end === -1)
|
|
3987
|
-
return null;
|
|
3988
|
-
const first = glob[i + 1];
|
|
3989
|
-
if (!first)
|
|
3990
|
-
return null;
|
|
3991
|
-
s += first;
|
|
3992
|
-
i = end;
|
|
3993
|
-
} else
|
|
3994
|
-
s += c;
|
|
3995
|
-
}
|
|
3996
|
-
return s;
|
|
3997
|
-
}
|
|
3998
|
-
function checkKeyArgs(command, args, rules) {
|
|
3999
|
-
if (rules.length === 0)
|
|
4000
|
-
return { ok: true };
|
|
4001
|
-
const spec = getCommandSpec(command);
|
|
4002
|
-
if (!spec)
|
|
4003
|
-
return { ok: true };
|
|
4004
|
-
const keyIndexes = expandKeyArity(spec.keyArity, args.length);
|
|
4005
|
-
for (const idx of keyIndexes) {
|
|
4006
|
-
const key = args[idx];
|
|
4007
|
-
if (key === undefined)
|
|
4008
|
-
continue;
|
|
4009
|
-
for (const pat of rules) {
|
|
4010
|
-
if (globToRegex(pat).test(key)) {
|
|
4011
|
-
return { ok: false, matchedKey: key, matchedPattern: pat };
|
|
4012
|
-
}
|
|
4013
|
-
}
|
|
4014
|
-
}
|
|
4015
|
-
if (spec.keyArity.kind === "pattern") {
|
|
4016
|
-
const userPat = args[spec.keyArity.argIndex];
|
|
4017
|
-
if (userPat !== undefined) {
|
|
4018
|
-
for (const pat of rules) {
|
|
4019
|
-
if (patternsOverlap(userPat, pat)) {
|
|
4020
|
-
return { ok: false, matchedKey: null, matchedPattern: pat };
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
}
|
|
4024
|
-
}
|
|
4025
|
-
return { ok: true };
|
|
4026
|
-
}
|
|
4027
|
-
function expandKeyArity(arity, argCount) {
|
|
4028
|
-
switch (arity.kind) {
|
|
4029
|
-
case "no-key":
|
|
4030
|
-
case "pattern":
|
|
4031
|
-
return [];
|
|
4032
|
-
case "single":
|
|
4033
|
-
return [arity.argIndex];
|
|
4034
|
-
case "multi-fixed":
|
|
4035
|
-
return arity.argIndices;
|
|
4036
|
-
case "multi-variable": {
|
|
4037
|
-
const indices = [];
|
|
4038
|
-
for (let i = arity.startIndex;i < argCount; i += arity.step)
|
|
4039
|
-
indices.push(i);
|
|
4040
|
-
return indices;
|
|
4041
|
-
}
|
|
4042
|
-
}
|
|
4043
|
-
}
|
|
2689
|
+
// src/adapters/redis/returned-key-names.ts
|
|
2690
|
+
var RETURNS_KEY_NAMES = new Set(["SCAN", "KEYS"]);
|
|
4044
2691
|
|
|
4045
2692
|
// src/adapters/redis/value-masker.ts
|
|
4046
|
-
var REDACTED = "[REDACTED]";
|
|
4047
2693
|
var MASKABLE = new Set(["GET", "GETRANGE", "HGETALL", "HGET", "HMGET", "HVALS"]);
|
|
4048
|
-
function planFor(key, rules) {
|
|
4049
|
-
const matched = rules.filter((r) => globToRegex(r.keyPattern).test(key));
|
|
4050
|
-
if (matched.length === 0)
|
|
4051
|
-
return null;
|
|
4052
|
-
const wholeValue = matched.some((r) => !r.fields || r.fields.length === 0);
|
|
4053
|
-
const fields = new Set(matched.flatMap((r) => r.fields ?? []));
|
|
4054
|
-
return { wholeValue, fields };
|
|
4055
|
-
}
|
|
4056
|
-
function maskRedisRows(command, args, rows, rules) {
|
|
4057
|
-
if (rules.length === 0)
|
|
4058
|
-
return rows;
|
|
4059
|
-
const head = command.toUpperCase();
|
|
4060
|
-
if (!MASKABLE.has(head))
|
|
4061
|
-
return rows;
|
|
4062
|
-
const key = args[0];
|
|
4063
|
-
if (key === undefined)
|
|
4064
|
-
return rows;
|
|
4065
|
-
const plan = planFor(key, rules);
|
|
4066
|
-
if (!plan)
|
|
4067
|
-
return rows;
|
|
4068
|
-
if (head === "GET" || head === "GETRANGE") {
|
|
4069
|
-
if (!plan.wholeValue)
|
|
4070
|
-
return rows;
|
|
4071
|
-
return rows.map((r) => ({ ...r, value: REDACTED }));
|
|
4072
|
-
}
|
|
4073
|
-
if (head === "HGETALL") {
|
|
4074
|
-
return rows.map((row) => {
|
|
4075
|
-
const out = {};
|
|
4076
|
-
for (const [field, value] of Object.entries(row)) {
|
|
4077
|
-
out[field] = plan.wholeValue || plan.fields.has(field) ? REDACTED : value;
|
|
4078
|
-
}
|
|
4079
|
-
return out;
|
|
4080
|
-
});
|
|
4081
|
-
}
|
|
4082
|
-
if (head === "HGET") {
|
|
4083
|
-
const field = args[1];
|
|
4084
|
-
const hit = plan.wholeValue || field !== undefined && plan.fields.has(field);
|
|
4085
|
-
return hit ? rows.map((r) => ({ ...r, value: REDACTED })) : rows;
|
|
4086
|
-
}
|
|
4087
|
-
if (head === "HMGET") {
|
|
4088
|
-
return rows.map((row) => {
|
|
4089
|
-
const idx = typeof row.index === "number" ? row.index : Number(row.index);
|
|
4090
|
-
const field = args[1 + idx];
|
|
4091
|
-
const hit = plan.wholeValue || field !== undefined && plan.fields.has(field);
|
|
4092
|
-
return hit ? { ...row, value: REDACTED } : row;
|
|
4093
|
-
});
|
|
4094
|
-
}
|
|
4095
|
-
if (head === "HVALS") {
|
|
4096
|
-
if (!plan.wholeValue)
|
|
4097
|
-
return rows;
|
|
4098
|
-
return rows.map((r) => ({ ...r, value: REDACTED }));
|
|
4099
|
-
}
|
|
4100
|
-
return rows;
|
|
4101
|
-
}
|
|
4102
|
-
|
|
4103
|
-
// src/adapters/redis-adapter.ts
|
|
4104
|
-
var REDIS_LIST_KEY_LIMIT = 1000;
|
|
4105
|
-
|
|
4106
|
-
class RedisAdapter {
|
|
4107
|
-
options;
|
|
4108
|
-
ClientClass;
|
|
4109
|
-
client = null;
|
|
4110
|
-
blacklistRules = [];
|
|
4111
|
-
constructor(options, ClientClass = null) {
|
|
4112
|
-
this.options = options;
|
|
4113
|
-
this.ClientClass = ClientClass;
|
|
4114
|
-
if (options.port < 1 || options.port > 65535) {
|
|
4115
|
-
throw new Error(`Invalid port number: ${options.port}`);
|
|
4116
|
-
}
|
|
4117
|
-
}
|
|
4118
|
-
setBlacklistRules(rules) {
|
|
4119
|
-
this.blacklistRules = rules;
|
|
4120
|
-
}
|
|
4121
|
-
maskRules = [];
|
|
4122
|
-
setMaskRules(rules) {
|
|
4123
|
-
this.maskRules = rules;
|
|
4124
|
-
}
|
|
4125
|
-
async resolveClientClass() {
|
|
4126
|
-
if (this.ClientClass)
|
|
4127
|
-
return this.ClientClass;
|
|
4128
|
-
const mod = await Promise.resolve(globalThis.Bun);
|
|
4129
|
-
return mod.RedisClient;
|
|
4130
|
-
}
|
|
4131
|
-
requireClient() {
|
|
4132
|
-
if (!this.client) {
|
|
4133
|
-
throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", ["\u8ACB\u5148\u547C\u53EB connect()"]);
|
|
4134
|
-
}
|
|
4135
|
-
return this.client;
|
|
4136
|
-
}
|
|
4137
|
-
async connect() {
|
|
4138
|
-
const ClientClass = await this.resolveClientClass();
|
|
4139
|
-
const url = buildRedisUrl(this.options);
|
|
4140
|
-
try {
|
|
4141
|
-
this.client = new ClientClass(url, {
|
|
4142
|
-
connectionTimeout: this.options.timeout ?? 5000,
|
|
4143
|
-
autoReconnect: false,
|
|
4144
|
-
maxRetries: 0,
|
|
4145
|
-
enableOfflineQueue: false
|
|
4146
|
-
});
|
|
4147
|
-
try {
|
|
4148
|
-
this.client.onclose = () => {};
|
|
4149
|
-
} catch {}
|
|
4150
|
-
await this.client.connect();
|
|
4151
|
-
} catch (err) {
|
|
4152
|
-
try {
|
|
4153
|
-
this.client?.close?.();
|
|
4154
|
-
} catch {}
|
|
4155
|
-
this.client = null;
|
|
4156
|
-
const message = err.message ?? "Unknown error";
|
|
4157
|
-
const code = inferConnectionCode(message, err.code);
|
|
4158
|
-
throw new ConnectionError(code, `Redis \u9023\u7DDA\u5931\u6557: ${message}`, [
|
|
4159
|
-
"\u8ACB\u78BA\u8A8D Redis \u670D\u52D9\u6B63\u5728\u57F7\u884C\uFF08\u4F8B\u5982\uFF1Aredis-cli ping\uFF09",
|
|
4160
|
-
`\u8ACB\u78BA\u8A8D host/port \u6B63\u78BA\uFF1A${this.options.host}:${this.options.port}`,
|
|
4161
|
-
"\u82E5\u6709\u8A2D\u5BC6\u78BC\u8ACB\u78BA\u8A8D password \u8207 ACL \u8A2D\u5B9A\u4E00\u81F4"
|
|
4162
|
-
]);
|
|
4163
|
-
}
|
|
4164
|
-
}
|
|
4165
|
-
async disconnect() {
|
|
4166
|
-
if (!this.client)
|
|
4167
|
-
return;
|
|
4168
|
-
try {
|
|
4169
|
-
this.client.close();
|
|
4170
|
-
} catch {} finally {
|
|
4171
|
-
this.client = null;
|
|
4172
|
-
}
|
|
4173
|
-
}
|
|
4174
|
-
async testConnection() {
|
|
4175
|
-
const client = this.requireClient();
|
|
4176
|
-
const reply = await client.send("PING", []);
|
|
4177
|
-
return reply === "PONG";
|
|
4178
|
-
}
|
|
4179
|
-
async getServerVersion() {
|
|
4180
|
-
const client = this.requireClient();
|
|
4181
|
-
const info = await client.send("INFO", ["server"]);
|
|
4182
|
-
const match = info.match(/^redis_version:(.+)$/m);
|
|
4183
|
-
return match?.[1]?.trim() ?? "unknown";
|
|
4184
|
-
}
|
|
4185
|
-
async listCollections(options) {
|
|
4186
|
-
const { names } = await this.sampleKeyNames(options?.limit ?? REDIS_LIST_KEY_LIMIT);
|
|
4187
|
-
return names.map((name) => ({ name }));
|
|
4188
|
-
}
|
|
4189
|
-
async sampleKeyNames(limit) {
|
|
4190
|
-
const client = this.requireClient();
|
|
4191
|
-
const rules = this.blacklistRules;
|
|
4192
|
-
const regexes = rules.map((p) => globToRegex(p));
|
|
4193
|
-
const { keys, scanned } = await scanAllKeys(client, "*", limit, limit, (key) => regexes.every((r) => !r.test(key)));
|
|
4194
|
-
return { names: keys, truncated: scanned >= limit };
|
|
4195
|
-
}
|
|
4196
|
-
async getDbSize() {
|
|
4197
|
-
const client = this.requireClient();
|
|
4198
|
-
const reply = await client.send("DBSIZE", []);
|
|
4199
|
-
return reply;
|
|
4200
|
-
}
|
|
4201
|
-
async listTables() {
|
|
4202
|
-
const collections = await this.listCollections();
|
|
4203
|
-
return collections.map((c) => ({ name: c.name, columns: [] }));
|
|
4204
|
-
}
|
|
4205
|
-
async getTableSchema(keyName) {
|
|
4206
|
-
const blk = checkKeyArgs("GET", [keyName], this.blacklistRules);
|
|
4207
|
-
if (!blk.ok) {
|
|
4208
|
-
throw new BlacklistRejection(`BlacklistRejection: schema lookup on key '${keyName}' rejected
|
|
4209
|
-
matched blacklist pattern: ${blk.matchedPattern}`, "SCHEMA", keyName, blk.matchedPattern);
|
|
4210
|
-
}
|
|
4211
|
-
const client = this.requireClient();
|
|
4212
|
-
const type = await client.send("TYPE", [keyName]);
|
|
4213
|
-
if (type === "none") {
|
|
4214
|
-
return { name: keyName, columns: [], tableType: "table" };
|
|
4215
|
-
}
|
|
4216
|
-
const ttl = await client.ttl(keyName);
|
|
4217
|
-
const sizeInfo = await readSizeInfo(client, keyName, type);
|
|
4218
|
-
const columns = [
|
|
4219
|
-
{ name: "type", type, nullable: false },
|
|
4220
|
-
{
|
|
4221
|
-
name: "ttl",
|
|
4222
|
-
type: ttl >= 0 ? `${ttl}s` : ttl === -1 ? "no expiry" : "missing",
|
|
4223
|
-
nullable: false
|
|
4224
|
-
},
|
|
4225
|
-
{ name: "size", type: String(sizeInfo.size), nullable: false }
|
|
4226
|
-
];
|
|
4227
|
-
if (sizeInfo.sample) {
|
|
4228
|
-
columns.push({ name: "sample", type: sizeInfo.sample, nullable: true });
|
|
4229
|
-
}
|
|
4230
|
-
return {
|
|
4231
|
-
name: keyName,
|
|
4232
|
-
columns,
|
|
4233
|
-
tableType: "table",
|
|
4234
|
-
estimatedRowCount: sizeInfo.size
|
|
4235
|
-
};
|
|
4236
|
-
}
|
|
4237
|
-
async dispatchCommand(head, rest) {
|
|
4238
|
-
const client = this.requireClient();
|
|
4239
|
-
const reply = await client.send(head, rest);
|
|
4240
|
-
return wrapReply(head, reply);
|
|
4241
|
-
}
|
|
4242
|
-
async execute(command, _params, options) {
|
|
4243
|
-
const tokens = parseRedisCommand(command);
|
|
4244
|
-
if (tokens.length === 0) {
|
|
4245
|
-
throw new Error("Redis \u6307\u4EE4\u4E0D\u53EF\u70BA\u7A7A");
|
|
4246
|
-
}
|
|
4247
|
-
const head = String(tokens[0]).toUpperCase();
|
|
4248
|
-
const rest = tokens.slice(1).map(String);
|
|
4249
|
-
const blk = checkKeyArgs(head, rest, this.blacklistRules);
|
|
4250
|
-
if (!blk.ok) {
|
|
4251
|
-
const msg = blk.matchedKey ? `BlacklistRejection: command ${head} on key '${blk.matchedKey}' rejected
|
|
4252
|
-
matched blacklist pattern: ${blk.matchedPattern}` : `BlacklistRejection: ${head} pattern overlaps blacklist pattern: ${blk.matchedPattern}`;
|
|
4253
|
-
throw new BlacklistRejection(msg, head, blk.matchedKey ?? null, blk.matchedPattern);
|
|
4254
|
-
}
|
|
4255
|
-
const noLimit = options?.noLimit === true;
|
|
4256
|
-
const rw = rewriteArgs(head, rest, { noLimit });
|
|
4257
|
-
const warnings = [];
|
|
4258
|
-
if (rw.warning)
|
|
4259
|
-
warnings.push(rw.warning);
|
|
4260
|
-
const spec = getCommandSpec(head);
|
|
4261
|
-
if (spec?.sizeGuard.kind === "truncate") {
|
|
4262
|
-
const client = this.requireClient();
|
|
4263
|
-
const rawReply = await client.send(head, rw.rewritten);
|
|
4264
|
-
const tr = truncateResult(head, rawReply, { noLimit });
|
|
4265
|
-
if (tr.warning)
|
|
4266
|
-
warnings.push(tr.warning);
|
|
4267
|
-
const rows2 = wrapReply(head, tr.value);
|
|
4268
|
-
const masked2 = maskRedisRows(head, rest, rows2, this.maskRules);
|
|
4269
|
-
return finishResult(masked2, warnings);
|
|
4270
|
-
}
|
|
4271
|
-
const rows = await this.dispatchCommand(head, rw.rewritten);
|
|
4272
|
-
const masked = maskRedisRows(head, rest, rows, this.maskRules);
|
|
4273
|
-
return finishResult(masked, warnings);
|
|
4274
|
-
}
|
|
4275
|
-
async insert(keyName, data) {
|
|
4276
|
-
const client = this.requireClient();
|
|
4277
|
-
const type = data.__type ?? "string";
|
|
4278
|
-
if (type === "string") {
|
|
4279
|
-
const value = String(data.value ?? "");
|
|
4280
|
-
await client.set(keyName, value);
|
|
4281
|
-
if (typeof data.ttl === "number" && data.ttl > 0) {
|
|
4282
|
-
await client.expire(keyName, data.ttl);
|
|
4283
|
-
}
|
|
4284
|
-
return { rows: [], affectedRows: 1 };
|
|
4285
|
-
}
|
|
4286
|
-
if (type === "hash") {
|
|
4287
|
-
const fields = data.fields;
|
|
4288
|
-
if (!fields)
|
|
4289
|
-
throw new Error("hash insert \u9700\u8981 fields \u7269\u4EF6");
|
|
4290
|
-
const flat = [];
|
|
4291
|
-
for (const [k, v] of Object.entries(fields))
|
|
4292
|
-
flat.push(k, String(v));
|
|
4293
|
-
await client.hmset(keyName, flat);
|
|
4294
|
-
return { rows: [], affectedRows: Object.keys(fields).length };
|
|
4295
|
-
}
|
|
4296
|
-
if (type === "list") {
|
|
4297
|
-
const values = data.values;
|
|
4298
|
-
if (!values || !Array.isArray(values))
|
|
4299
|
-
throw new Error("list insert \u9700\u8981 values \u9663\u5217");
|
|
4300
|
-
await client.send("RPUSH", [keyName, ...values.map(String)]);
|
|
4301
|
-
return { rows: [], affectedRows: values.length };
|
|
4302
|
-
}
|
|
4303
|
-
if (type === "set") {
|
|
4304
|
-
const values = data.values;
|
|
4305
|
-
if (!values || !Array.isArray(values))
|
|
4306
|
-
throw new Error("set insert \u9700\u8981 values \u9663\u5217");
|
|
4307
|
-
await client.send("SADD", [keyName, ...values.map(String)]);
|
|
4308
|
-
return { rows: [], affectedRows: values.length };
|
|
4309
|
-
}
|
|
4310
|
-
if (type === "zset") {
|
|
4311
|
-
const members = data.members;
|
|
4312
|
-
if (!members)
|
|
4313
|
-
throw new Error("zset insert \u9700\u8981 members \u7269\u4EF6 (member -> score)");
|
|
4314
|
-
const flat = [];
|
|
4315
|
-
for (const [m, s] of Object.entries(members))
|
|
4316
|
-
flat.push(String(s), m);
|
|
4317
|
-
await client.send("ZADD", [keyName, ...flat]);
|
|
4318
|
-
return { rows: [], affectedRows: Object.keys(members).length };
|
|
4319
|
-
}
|
|
4320
|
-
throw new Error(`\u4E0D\u652F\u63F4\u7684 insert \u985E\u578B: ${String(type)}`);
|
|
4321
|
-
}
|
|
4322
|
-
async update(keyName, _filter, update) {
|
|
4323
|
-
const client = this.requireClient();
|
|
4324
|
-
const fields = update.fields;
|
|
4325
|
-
if (fields) {
|
|
4326
|
-
const flat = [];
|
|
4327
|
-
for (const [k, v] of Object.entries(fields))
|
|
4328
|
-
flat.push(k, String(v));
|
|
4329
|
-
await client.hmset(keyName, flat);
|
|
4330
|
-
return { rows: [], affectedRows: Object.keys(fields).length };
|
|
4331
|
-
}
|
|
4332
|
-
const type = update.__type;
|
|
4333
|
-
if (type === "list") {
|
|
4334
|
-
const values = update.values;
|
|
4335
|
-
await client.send("RPUSH", [keyName, ...values.map(String)]);
|
|
4336
|
-
return { rows: [], affectedRows: values.length };
|
|
4337
|
-
}
|
|
4338
|
-
if (type === "set") {
|
|
4339
|
-
const values = update.values;
|
|
4340
|
-
await client.send("SADD", [keyName, ...values.map(String)]);
|
|
4341
|
-
return { rows: [], affectedRows: values.length };
|
|
4342
|
-
}
|
|
4343
|
-
if (type === "zset") {
|
|
4344
|
-
const members = update.members;
|
|
4345
|
-
const flat = [];
|
|
4346
|
-
for (const [m, s] of Object.entries(members))
|
|
4347
|
-
flat.push(String(s), m);
|
|
4348
|
-
await client.send("ZADD", [keyName, ...flat]);
|
|
4349
|
-
return { rows: [], affectedRows: Object.keys(members).length };
|
|
4350
|
-
}
|
|
4351
|
-
if ("value" in update) {
|
|
4352
|
-
await client.set(keyName, String(update.value));
|
|
4353
|
-
return { rows: [], affectedRows: 1 };
|
|
4354
|
-
}
|
|
4355
|
-
throw new Error("update \u9700\u8981 fields, values, members \u6216 value");
|
|
4356
|
-
}
|
|
4357
|
-
async delete(keyName, filter) {
|
|
4358
|
-
const client = this.requireClient();
|
|
4359
|
-
const field = filter.field;
|
|
4360
|
-
if (field) {
|
|
4361
|
-
const removed2 = await client.send("HDEL", [keyName, field]);
|
|
4362
|
-
return { rows: [], affectedRows: removed2 };
|
|
4363
|
-
}
|
|
4364
|
-
const value = filter.value;
|
|
4365
|
-
if (value) {
|
|
4366
|
-
const type = await client.send("TYPE", [keyName]);
|
|
4367
|
-
let removed2 = 0;
|
|
4368
|
-
if (type === "list") {
|
|
4369
|
-
removed2 = await client.send("LREM", [keyName, "0", value]);
|
|
4370
|
-
} else if (type === "set") {
|
|
4371
|
-
removed2 = await client.send("SREM", [keyName, value]);
|
|
4372
|
-
} else if (type === "zset") {
|
|
4373
|
-
removed2 = await client.send("ZREM", [keyName, value]);
|
|
4374
|
-
}
|
|
4375
|
-
return { rows: [], affectedRows: removed2 };
|
|
4376
|
-
}
|
|
4377
|
-
const removed = await client.del(keyName);
|
|
4378
|
-
return { rows: [], affectedRows: removed };
|
|
4379
|
-
}
|
|
4380
|
-
}
|
|
4381
|
-
function finishResult(rows, warnings) {
|
|
4382
|
-
const columnNames = rows[0] ? Object.keys(rows[0]) : ["value"];
|
|
4383
|
-
return {
|
|
4384
|
-
rows,
|
|
4385
|
-
affectedRows: rows.length,
|
|
4386
|
-
rowCount: rows.length,
|
|
4387
|
-
columnNames,
|
|
4388
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
4389
|
-
};
|
|
4390
|
-
}
|
|
4391
|
-
function buildRedisUrl(opts) {
|
|
4392
|
-
let auth = "";
|
|
4393
|
-
if (opts.user || opts.password) {
|
|
4394
|
-
const u = encodeURIComponent(opts.user || "");
|
|
4395
|
-
const p = encodeURIComponent(opts.password || "");
|
|
4396
|
-
auth = `${u}:${p}@`;
|
|
4397
|
-
}
|
|
4398
|
-
const dbPart = opts.database ? `/${opts.database}` : "";
|
|
4399
|
-
return `redis://${auth}${opts.host}:${opts.port}${dbPart}`;
|
|
4400
|
-
}
|
|
4401
|
-
function inferConnectionCode(message, driverCode) {
|
|
4402
|
-
const m = message.toLowerCase();
|
|
4403
|
-
if (driverCode === "ERR_REDIS_AUTHENTICATION_FAILED")
|
|
4404
|
-
return "AUTH_FAILED";
|
|
4405
|
-
if (driverCode === "ERR_REDIS_CONNECTION_CLOSED")
|
|
4406
|
-
return "ECONNREFUSED";
|
|
4407
|
-
if (driverCode === "ECONNREFUSED" || m.includes("econnrefused") || m.includes("refused")) {
|
|
4408
|
-
return "ECONNREFUSED";
|
|
4409
|
-
}
|
|
4410
|
-
if (driverCode === "ETIMEDOUT" || m.includes("timeout") || m.includes("timed out")) {
|
|
4411
|
-
return "ETIMEDOUT";
|
|
4412
|
-
}
|
|
4413
|
-
if (driverCode === "ENOTFOUND" || m.includes("enotfound") || m.includes("getaddrinfo")) {
|
|
4414
|
-
return "ENOTFOUND";
|
|
4415
|
-
}
|
|
4416
|
-
if (m.includes("noauth") || m.includes("wrongpass") || m.includes("auth")) {
|
|
4417
|
-
return "AUTH_FAILED";
|
|
4418
|
-
}
|
|
4419
|
-
return "UNKNOWN";
|
|
4420
|
-
}
|
|
4421
|
-
function parseRedisCommand(input) {
|
|
4422
|
-
const tokens = [];
|
|
4423
|
-
let buf = "";
|
|
4424
|
-
let quote = null;
|
|
4425
|
-
let i = 0;
|
|
4426
|
-
let inToken = false;
|
|
4427
|
-
const push = () => {
|
|
4428
|
-
tokens.push(buf);
|
|
4429
|
-
buf = "";
|
|
4430
|
-
inToken = false;
|
|
4431
|
-
};
|
|
4432
|
-
while (i < input.length) {
|
|
4433
|
-
const ch = input[i];
|
|
4434
|
-
if (quote) {
|
|
4435
|
-
if (ch === "\\" && i + 1 < input.length) {
|
|
4436
|
-
buf += input[i + 1];
|
|
4437
|
-
i += 2;
|
|
4438
|
-
continue;
|
|
4439
|
-
}
|
|
4440
|
-
if (ch === quote) {
|
|
4441
|
-
quote = null;
|
|
4442
|
-
i++;
|
|
4443
|
-
continue;
|
|
4444
|
-
}
|
|
4445
|
-
buf += ch;
|
|
4446
|
-
i++;
|
|
4447
|
-
continue;
|
|
4448
|
-
}
|
|
4449
|
-
if (ch === '"' || ch === "'") {
|
|
4450
|
-
quote = ch;
|
|
4451
|
-
inToken = true;
|
|
4452
|
-
i++;
|
|
4453
|
-
continue;
|
|
4454
|
-
}
|
|
4455
|
-
if (/\s/.test(ch)) {
|
|
4456
|
-
if (inToken)
|
|
4457
|
-
push();
|
|
4458
|
-
i++;
|
|
4459
|
-
continue;
|
|
4460
|
-
}
|
|
4461
|
-
if (ch === "\\" && i + 1 < input.length) {
|
|
4462
|
-
buf += input[i + 1];
|
|
4463
|
-
i += 2;
|
|
4464
|
-
inToken = true;
|
|
4465
|
-
continue;
|
|
4466
|
-
}
|
|
4467
|
-
buf += ch;
|
|
4468
|
-
inToken = true;
|
|
4469
|
-
i++;
|
|
4470
|
-
}
|
|
4471
|
-
if (quote) {
|
|
4472
|
-
throw new Error("Redis \u6307\u4EE4\u542B\u6709\u672A\u9589\u5408\u7684\u5F15\u865F");
|
|
4473
|
-
}
|
|
4474
|
-
if (inToken)
|
|
4475
|
-
push();
|
|
4476
|
-
return tokens;
|
|
4477
|
-
}
|
|
4478
|
-
async function scanAllKeys(client, pattern, count, maxKeys, accept = () => true) {
|
|
4479
|
-
const seen = new Set;
|
|
4480
|
-
const kept = new Set;
|
|
4481
|
-
let cursor = "0";
|
|
4482
|
-
do {
|
|
4483
|
-
const reply = await client.send("SCAN", [
|
|
4484
|
-
cursor,
|
|
4485
|
-
"MATCH",
|
|
4486
|
-
pattern,
|
|
4487
|
-
"COUNT",
|
|
4488
|
-
String(count)
|
|
4489
|
-
]);
|
|
4490
|
-
const [next, batch] = reply;
|
|
4491
|
-
for (const k of batch) {
|
|
4492
|
-
if (seen.has(k))
|
|
4493
|
-
continue;
|
|
4494
|
-
seen.add(k);
|
|
4495
|
-
if (accept(k))
|
|
4496
|
-
kept.add(k);
|
|
4497
|
-
}
|
|
4498
|
-
cursor = next;
|
|
4499
|
-
if (seen.size >= maxKeys)
|
|
4500
|
-
break;
|
|
4501
|
-
} while (cursor !== "0");
|
|
4502
|
-
return { keys: Array.from(kept), scanned: seen.size };
|
|
4503
|
-
}
|
|
4504
|
-
async function readSizeInfo(client, key, type) {
|
|
4505
|
-
switch (type) {
|
|
4506
|
-
case "string": {
|
|
4507
|
-
const len = await client.send("STRLEN", [key]);
|
|
4508
|
-
return { size: len };
|
|
4509
|
-
}
|
|
4510
|
-
case "hash": {
|
|
4511
|
-
const len = await client.send("HLEN", [key]);
|
|
4512
|
-
const fields = await client.send("HKEYS", [key]);
|
|
4513
|
-
return { size: len, sample: fields.slice(0, 5).join(", ") };
|
|
4514
|
-
}
|
|
4515
|
-
case "list": {
|
|
4516
|
-
const len = await client.send("LLEN", [key]);
|
|
4517
|
-
return { size: len };
|
|
4518
|
-
}
|
|
4519
|
-
case "set": {
|
|
4520
|
-
const len = await client.send("SCARD", [key]);
|
|
4521
|
-
return { size: len };
|
|
4522
|
-
}
|
|
4523
|
-
case "zset": {
|
|
4524
|
-
const len = await client.send("ZCARD", [key]);
|
|
4525
|
-
return { size: len };
|
|
4526
|
-
}
|
|
4527
|
-
case "stream": {
|
|
4528
|
-
const len = await client.send("XLEN", [key]);
|
|
4529
|
-
return { size: len };
|
|
4530
|
-
}
|
|
4531
|
-
default:
|
|
4532
|
-
return { size: 0 };
|
|
4533
|
-
}
|
|
4534
|
-
}
|
|
4535
|
-
function wrapReply(command, reply) {
|
|
4536
|
-
const upper = command.toUpperCase();
|
|
4537
|
-
if (reply === null || reply === undefined)
|
|
4538
|
-
return [];
|
|
4539
|
-
if (typeof reply === "string" || typeof reply === "number" || typeof reply === "boolean" || Buffer.isBuffer(reply)) {
|
|
4540
|
-
return [{ value: reply.toString() }];
|
|
4541
|
-
}
|
|
4542
|
-
if (Array.isArray(reply)) {
|
|
4543
|
-
if (upper === "HGETALL") {
|
|
4544
|
-
const obj = {};
|
|
4545
|
-
for (let i = 0;i < reply.length; i += 2) {
|
|
4546
|
-
obj[String(reply[i])] = reply[i + 1];
|
|
4547
|
-
}
|
|
4548
|
-
return [obj];
|
|
4549
|
-
}
|
|
4550
|
-
return reply.map((value, idx) => ({ index: idx, value: String(value) }));
|
|
4551
|
-
}
|
|
4552
|
-
if (typeof reply === "object") {
|
|
4553
|
-
return [reply];
|
|
4554
|
-
}
|
|
4555
|
-
return [{ value: String(reply) }];
|
|
4556
|
-
}
|
|
4557
|
-
|
|
4558
|
-
// src/adapters/elasticsearch-adapter.ts
|
|
4559
|
-
class ElasticsearchAdapter {
|
|
4560
|
-
options;
|
|
4561
|
-
baseUrl = "";
|
|
4562
|
-
currentNodeIndex = 0;
|
|
4563
|
-
constructor(options) {
|
|
4564
|
-
this.options = options;
|
|
4565
|
-
this.baseUrl = this.resolveBaseUrl();
|
|
4566
|
-
}
|
|
4567
|
-
async connect() {
|
|
4568
|
-
try {
|
|
4569
|
-
await this.request("GET", "/");
|
|
4570
|
-
} catch (error) {
|
|
4571
|
-
if (error instanceof ConnectionError)
|
|
4572
|
-
throw error;
|
|
4573
|
-
throw new ConnectionError("UNKNOWN", `Could not connect to Elasticsearch: ${error.message}`, ["Check if Elasticsearch is running", "Verify host and port", "Check network connectivity"]);
|
|
4574
|
-
}
|
|
4575
|
-
}
|
|
4576
|
-
async disconnect() {}
|
|
4577
|
-
async execute(query, params, options) {
|
|
4578
|
-
const indexName = params?.[0];
|
|
4579
|
-
if (!indexName) {
|
|
4580
|
-
throw new Error("Index name is required as the first parameter to execute()");
|
|
4581
|
-
}
|
|
4582
|
-
const isDsl = query.trim().startsWith("{");
|
|
4583
|
-
const limit = options?.limit ?? 100;
|
|
4584
|
-
let path = `/${encodeEsIndexExpression(indexName)}/_search`;
|
|
4585
|
-
let body = undefined;
|
|
4586
|
-
if (isDsl) {
|
|
4587
|
-
body = JSON.parse(query);
|
|
4588
|
-
assertNoElasticsearchScript(body);
|
|
4589
|
-
if (body.size === undefined) {
|
|
4590
|
-
body.size = limit;
|
|
4591
|
-
}
|
|
4592
|
-
} else {
|
|
4593
|
-
path += `?q=${encodeURIComponent(query)}&size=${limit}`;
|
|
4594
|
-
}
|
|
4595
|
-
const response = await this.request(body ? "POST" : "GET", path, body);
|
|
4596
|
-
const hits = response.hits?.hits || [];
|
|
4597
|
-
const rows = hits.map((hit) => {
|
|
4598
|
-
const row = { _id: hit._id };
|
|
4599
|
-
this.flattenSource("", hit._source || {}, row);
|
|
4600
|
-
return row;
|
|
4601
|
-
});
|
|
4602
|
-
return {
|
|
4603
|
-
rows,
|
|
4604
|
-
affectedRows: rows.length,
|
|
4605
|
-
rowCount: rows.length,
|
|
4606
|
-
columnNames: rows[0] ? Object.keys(rows[0]) : []
|
|
4607
|
-
};
|
|
4608
|
-
}
|
|
4609
|
-
flattenSource(prefix, source, target) {
|
|
4610
|
-
for (const [key, value] of Object.entries(source)) {
|
|
4611
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
4612
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
4613
|
-
this.flattenSource(fullKey, value, target);
|
|
4614
|
-
} else {
|
|
4615
|
-
target[fullKey] = value;
|
|
4616
|
-
}
|
|
4617
|
-
}
|
|
4618
|
-
}
|
|
4619
|
-
async listCollections(options) {
|
|
4620
|
-
const rows = await this.request("GET", "/_cat/indices?format=json&h=index,docs.count&expand_wildcards=all");
|
|
4621
|
-
return (Array.isArray(rows) ? rows : []).filter((row) => typeof row.index === "string" && row.index.length > 0).filter((row) => options?.includeSystem || !row.index.startsWith(".")).map((row) => {
|
|
4622
|
-
const parsed = Number(row["docs.count"]);
|
|
4623
|
-
return { name: row.index, documentCount: Number.isFinite(parsed) ? parsed : 0 };
|
|
4624
|
-
});
|
|
4625
|
-
}
|
|
4626
|
-
async listTables(options) {
|
|
4627
|
-
const collections = await this.listCollections(options);
|
|
4628
|
-
return collections.map((c) => ({
|
|
4629
|
-
name: c.name,
|
|
4630
|
-
columns: [],
|
|
4631
|
-
estimatedRowCount: c.documentCount,
|
|
4632
|
-
tableType: "table"
|
|
4633
|
-
}));
|
|
4634
|
-
}
|
|
4635
|
-
async getTableSchema(tableName, _options) {
|
|
4636
|
-
const mapping = await this.request("GET", `/${encodeEsIndexExpression(tableName)}/_mapping`);
|
|
4637
|
-
const indexMapping = mapping[tableName] || Object.values(mapping)[0];
|
|
4638
|
-
if (!indexMapping) {
|
|
4639
|
-
throw new Error(`Index not found: ${tableName}`);
|
|
4640
|
-
}
|
|
4641
|
-
const columns = [];
|
|
4642
|
-
this.flattenProperties("", indexMapping.mappings?.properties || {}, columns);
|
|
4643
|
-
return {
|
|
4644
|
-
name: tableName,
|
|
4645
|
-
columns,
|
|
4646
|
-
tableType: "table"
|
|
4647
|
-
};
|
|
4648
|
-
}
|
|
4649
|
-
flattenProperties(prefix, properties, columns) {
|
|
4650
|
-
for (const [name, config] of Object.entries(properties)) {
|
|
4651
|
-
const fullName = prefix ? `${prefix}.${name}` : name;
|
|
4652
|
-
if (config.properties) {
|
|
4653
|
-
this.flattenProperties(fullName, config.properties, columns);
|
|
4654
|
-
} else {
|
|
4655
|
-
columns.push({
|
|
4656
|
-
name: fullName,
|
|
4657
|
-
type: config.type || "object",
|
|
4658
|
-
nullable: true
|
|
4659
|
-
});
|
|
4660
|
-
if (config.fields) {
|
|
4661
|
-
for (const [fieldName, fieldConfig] of Object.entries(config.fields)) {
|
|
4662
|
-
columns.push({
|
|
4663
|
-
name: `${fullName}.${fieldName}`,
|
|
4664
|
-
type: fieldConfig.type,
|
|
4665
|
-
nullable: true
|
|
4666
|
-
});
|
|
4667
|
-
}
|
|
4668
|
-
}
|
|
4669
|
-
}
|
|
4670
|
-
}
|
|
4671
|
-
}
|
|
4672
|
-
async testConnection() {
|
|
4673
|
-
try {
|
|
4674
|
-
await this.connect();
|
|
4675
|
-
return true;
|
|
4676
|
-
} catch {
|
|
4677
|
-
return false;
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4680
|
-
async getServerVersion() {
|
|
4681
|
-
const info = await this.request("GET", "/");
|
|
4682
|
-
return info.version?.number ?? "unknown";
|
|
4683
|
-
}
|
|
4684
|
-
async insert(collection, data) {
|
|
4685
|
-
const id = data._id || data.id;
|
|
4686
|
-
const body = { ...data };
|
|
4687
|
-
delete body._id;
|
|
4688
|
-
delete body.id;
|
|
4689
|
-
const encodedId = id ? encodeEsPathSegment(id) : "";
|
|
4690
|
-
const response = await this.request("PUT", `/${encodeEsIndexExpression(collection)}/_doc/${encodedId}`, body);
|
|
4691
|
-
return {
|
|
4692
|
-
rows: [],
|
|
4693
|
-
affectedRows: response.result === "created" || response.result === "updated" ? 1 : 0,
|
|
4694
|
-
lastInsertId: response._id
|
|
4695
|
-
};
|
|
4696
|
-
}
|
|
4697
|
-
async update(collection, filter, update) {
|
|
4698
|
-
const id = filter._id || filter.id;
|
|
4699
|
-
if (!id) {
|
|
4700
|
-
throw new Error("Elasticsearch update requires _id or id in filter");
|
|
4701
|
-
}
|
|
4702
|
-
const response = await this.request("POST", `/${encodeEsIndexExpression(collection)}/_update/${encodeEsPathSegment(id)}`, {
|
|
4703
|
-
doc: update
|
|
4704
|
-
});
|
|
4705
|
-
return {
|
|
4706
|
-
rows: [],
|
|
4707
|
-
affectedRows: response.result === "updated" || response.result === "noop" ? 1 : 0
|
|
4708
|
-
};
|
|
4709
|
-
}
|
|
4710
|
-
async delete(collection, filter) {
|
|
4711
|
-
const id = filter._id || filter.id;
|
|
4712
|
-
if (!id) {
|
|
4713
|
-
throw new Error("Elasticsearch delete requires _id or id in filter");
|
|
4714
|
-
}
|
|
4715
|
-
const response = await this.request("DELETE", `/${encodeEsIndexExpression(collection)}/_doc/${encodeEsPathSegment(id)}`);
|
|
4716
|
-
return {
|
|
4717
|
-
rows: [],
|
|
4718
|
-
affectedRows: response.result === "deleted" ? 1 : 0
|
|
4719
|
-
};
|
|
4720
|
-
}
|
|
4721
|
-
resolveBaseUrl() {
|
|
4722
|
-
if (this.options.cloudId) {
|
|
4723
|
-
return this.decodeCloudId(this.options.cloudId);
|
|
4724
|
-
}
|
|
4725
|
-
const nodes = this.options.nodes;
|
|
4726
|
-
if (nodes && nodes.length > 0) {
|
|
4727
|
-
return (nodes[0] ?? "").replace(/\/$/, "");
|
|
4728
|
-
}
|
|
4729
|
-
const protocol = this.options.protocol ?? "https";
|
|
4730
|
-
const host = this.options.host || "localhost";
|
|
4731
|
-
const port = this.options.port || 9200;
|
|
4732
|
-
return `${protocol}://${host}:${port}`;
|
|
4733
|
-
}
|
|
4734
|
-
decodeCloudId(cloudId) {
|
|
4735
|
-
try {
|
|
4736
|
-
const parts = cloudId.split(":");
|
|
4737
|
-
if (parts.length !== 2)
|
|
4738
|
-
throw new Error("Invalid format");
|
|
4739
|
-
const base64 = parts[1] ?? "";
|
|
4740
|
-
const decoded = atob(base64);
|
|
4741
|
-
const [hostAndPort = "", ...rest] = decoded.split("$");
|
|
4742
|
-
if (hostAndPort.includes(".")) {
|
|
4743
|
-
const [host = "", port] = hostAndPort.split(":");
|
|
4744
|
-
return `https://${rest[0]}.${host}:${port || 443}`;
|
|
4745
|
-
}
|
|
4746
|
-
return `https://${rest[0]}.${hostAndPort}:443`;
|
|
4747
|
-
} catch (error) {
|
|
4748
|
-
throw new Error(`Invalid Cloud ID: ${error.message}`);
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
async getHeaders() {
|
|
4752
|
-
const headers = {
|
|
4753
|
-
"Content-Type": "application/json"
|
|
4754
|
-
};
|
|
4755
|
-
if (this.options.apiKey) {
|
|
4756
|
-
headers["Authorization"] = `ApiKey ${this.options.apiKey}`;
|
|
4757
|
-
} else if (this.options.user && this.options.password) {
|
|
4758
|
-
const auth = btoa(`${this.options.user}:${this.options.password}`);
|
|
4759
|
-
headers["Authorization"] = `Basic ${auth}`;
|
|
4760
|
-
}
|
|
4761
|
-
return headers;
|
|
4762
|
-
}
|
|
4763
|
-
getBaseUrl() {
|
|
4764
|
-
return this.baseUrl;
|
|
4765
|
-
}
|
|
4766
|
-
async request(method, path, body, options) {
|
|
4767
|
-
const url = `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
4768
|
-
const headers = await this.getHeaders();
|
|
4769
|
-
const controller = new AbortController;
|
|
4770
|
-
const timeout = options?.timeout ?? this.options.timeout ?? 5000;
|
|
4771
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
4772
|
-
try {
|
|
4773
|
-
const response = await fetch(url, {
|
|
4774
|
-
method,
|
|
4775
|
-
headers,
|
|
4776
|
-
body: body ? typeof body === "string" ? body : JSON.stringify(body) : undefined,
|
|
4777
|
-
signal: controller.signal,
|
|
4778
|
-
tls: {
|
|
4779
|
-
rejectUnauthorized: this.options.rejectUnauthorized ?? true,
|
|
4780
|
-
ca: this.options.caPath ? await Bun.file(this.options.caPath).text() : undefined
|
|
4781
|
-
}
|
|
4782
|
-
});
|
|
4783
|
-
if (!response.ok) {
|
|
4784
|
-
await this.handleErrorResponse(response);
|
|
4785
|
-
}
|
|
4786
|
-
return await response.json();
|
|
4787
|
-
} catch (error) {
|
|
4788
|
-
if (error.name === "AbortError") {
|
|
4789
|
-
throw new ConnectionError("ETIMEDOUT", `Request timed out after ${timeout}ms`, [
|
|
4790
|
-
"Increase timeout in config if the cluster is slow",
|
|
4791
|
-
"Check if the network is congested"
|
|
4792
|
-
]);
|
|
4793
|
-
}
|
|
4794
|
-
if (error instanceof ConnectionError)
|
|
4795
|
-
throw error;
|
|
4796
|
-
const msg = error.message.toLowerCase();
|
|
4797
|
-
if (msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
4798
|
-
throw new ConnectionError("ECONNREFUSED", `Connection refused at ${this.baseUrl}`, [
|
|
4799
|
-
"Check if Elasticsearch is running",
|
|
4800
|
-
"Verify host and port"
|
|
4801
|
-
]);
|
|
4802
|
-
}
|
|
4803
|
-
if (msg.includes("not found") || msg.includes("enotfound")) {
|
|
4804
|
-
throw new ConnectionError("ENOTFOUND", `Host not found: ${this.baseUrl}`, [
|
|
4805
|
-
"Verify the hostname is correct",
|
|
4806
|
-
"Check DNS settings"
|
|
4807
|
-
]);
|
|
4808
|
-
}
|
|
4809
|
-
throw error;
|
|
4810
|
-
} finally {
|
|
4811
|
-
clearTimeout(timeoutId);
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
async handleErrorResponse(response) {
|
|
4815
|
-
let message = "";
|
|
4816
|
-
try {
|
|
4817
|
-
const errorBody = await response.json();
|
|
4818
|
-
message = errorBody?.error?.reason || errorBody?.message || response.statusText;
|
|
4819
|
-
} catch {
|
|
4820
|
-
try {
|
|
4821
|
-
message = await response.text();
|
|
4822
|
-
} catch {
|
|
4823
|
-
message = response.statusText;
|
|
4824
|
-
}
|
|
4825
|
-
}
|
|
4826
|
-
const status = response.status;
|
|
4827
|
-
if (status === 401) {
|
|
4828
|
-
throw new ConnectionError("AUTH_FAILED", `Authentication failed: ${message}`, [
|
|
4829
|
-
"Check your API key or username/password",
|
|
4830
|
-
"Verify if the user has required roles"
|
|
4831
|
-
]);
|
|
4832
|
-
}
|
|
4833
|
-
if (status === 403) {
|
|
4834
|
-
throw new ConnectionError("AUTH_FAILED", `Permission denied: ${message}`, [
|
|
4835
|
-
"User does not have permission to perform this action"
|
|
4836
|
-
]);
|
|
4837
|
-
}
|
|
4838
|
-
throw new ConnectionError("UNKNOWN", `Elasticsearch error (${status}): ${message}`, [
|
|
4839
|
-
"Check Elasticsearch logs for more details",
|
|
4840
|
-
"Verify the request body is valid ES DSL"
|
|
4841
|
-
]);
|
|
4842
|
-
}
|
|
4843
|
-
}
|
|
4844
|
-
|
|
4845
|
-
// src/utils/connection-timeout.ts
|
|
4846
|
-
var _globalConnectionTimeout;
|
|
4847
|
-
var _globalStatementTimeout;
|
|
4848
|
-
function resolveConnectionTimeout(configured) {
|
|
4849
|
-
return _globalConnectionTimeout ?? configured;
|
|
4850
|
-
}
|
|
4851
|
-
function resolveStatementTimeout(configured) {
|
|
4852
|
-
return _globalStatementTimeout ?? configured;
|
|
4853
|
-
}
|
|
4854
|
-
function withResolvedTimeout(options) {
|
|
4855
|
-
const timeout = resolveConnectionTimeout(options.timeout);
|
|
4856
|
-
const statementTimeout = resolveStatementTimeout(options.statementTimeout);
|
|
4857
|
-
if (timeout === options.timeout && statementTimeout === options.statementTimeout)
|
|
4858
|
-
return options;
|
|
4859
|
-
return { ...options, timeout, statementTimeout };
|
|
4860
|
-
}
|
|
4861
|
-
|
|
4862
|
-
// src/adapters/factory.ts
|
|
4863
|
-
class AdapterFactory {
|
|
4864
|
-
static createSqlAdapter(rawOptions) {
|
|
4865
|
-
const options = withResolvedTimeout(rawOptions);
|
|
4866
|
-
switch (options.system) {
|
|
4867
|
-
case "postgresql":
|
|
4868
|
-
return new PostgreSQLAdapter(options);
|
|
4869
|
-
case "mysql":
|
|
4870
|
-
case "mariadb":
|
|
4871
|
-
return new MySQLAdapter(options);
|
|
4872
|
-
default:
|
|
4873
|
-
throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
|
|
4874
|
-
}
|
|
4875
|
-
}
|
|
4876
|
-
static createQueryableAdapter(rawOptions) {
|
|
4877
|
-
const options = withResolvedTimeout(rawOptions);
|
|
4878
|
-
switch (options.system) {
|
|
4879
|
-
case "mongodb":
|
|
4880
|
-
return new MongoDBAdapter(options);
|
|
4881
|
-
case "redis":
|
|
4882
|
-
return new RedisAdapter(options);
|
|
4883
|
-
case "elasticsearch":
|
|
4884
|
-
return new ElasticsearchAdapter(options);
|
|
4885
|
-
default:
|
|
4886
|
-
throw new Error(`createQueryableAdapter requires a non-SQL queryable system, got: ${options.system}`);
|
|
4887
|
-
}
|
|
4888
|
-
}
|
|
4889
|
-
static createAdapter(options) {
|
|
4890
|
-
switch (options.system) {
|
|
4891
|
-
case "postgresql":
|
|
4892
|
-
case "mysql":
|
|
4893
|
-
case "mariadb":
|
|
4894
|
-
return AdapterFactory.createSqlAdapter(options);
|
|
4895
|
-
case "mongodb":
|
|
4896
|
-
case "redis":
|
|
4897
|
-
case "elasticsearch":
|
|
4898
|
-
return AdapterFactory.createQueryableAdapter(options);
|
|
4899
|
-
default:
|
|
4900
|
-
throw new Error(`Unsupported database system: ${options.system}`);
|
|
4901
|
-
}
|
|
4902
|
-
}
|
|
4903
|
-
static createMongoDBAdapter(options) {
|
|
4904
|
-
if (options.system !== "mongodb") {
|
|
4905
|
-
throw new Error("createMongoDBAdapter requires system: mongodb");
|
|
4906
|
-
}
|
|
4907
|
-
return AdapterFactory.createQueryableAdapter(options);
|
|
4908
|
-
}
|
|
4909
|
-
static createRedisAdapter(rawOptions, blacklistRules = [], maskRules = []) {
|
|
4910
|
-
if (rawOptions.system !== "redis") {
|
|
4911
|
-
throw new Error("createRedisAdapter requires system: redis");
|
|
4912
|
-
}
|
|
4913
|
-
const options = withResolvedTimeout(rawOptions);
|
|
4914
|
-
const adapter = new RedisAdapter(options);
|
|
4915
|
-
adapter.setBlacklistRules(blacklistRules);
|
|
4916
|
-
adapter.setMaskRules(maskRules);
|
|
4917
|
-
return adapter;
|
|
4918
|
-
}
|
|
4919
|
-
static createElasticsearchAdapter(options) {
|
|
4920
|
-
if (options.system !== "elasticsearch") {
|
|
4921
|
-
throw new Error("createElasticsearchAdapter requires system: elasticsearch");
|
|
4922
|
-
}
|
|
4923
|
-
return AdapterFactory.createQueryableAdapter(options);
|
|
4924
|
-
}
|
|
4925
|
-
}
|
|
4926
2694
|
// src/utils/sql-lexical.ts
|
|
4927
2695
|
var IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
|
|
4928
2696
|
var IDENTIFIER_START = /[A-Za-z_]|[\u0080-\uFFFF]/;
|
|
@@ -10349,10 +8117,26 @@ var MetadataSchema = exports_external.object({
|
|
|
10349
8117
|
schemaLastUpdated: exports_external.string().datetime().optional(),
|
|
10350
8118
|
schemaTableCount: exports_external.number().int().nonnegative().optional()
|
|
10351
8119
|
}).optional().default({});
|
|
8120
|
+
var BLACKLIST_KEY_ALIASES = {
|
|
8121
|
+
indices: "tables",
|
|
8122
|
+
index: "tables",
|
|
8123
|
+
fields: "columns",
|
|
8124
|
+
keys: "tables"
|
|
8125
|
+
};
|
|
10352
8126
|
var BlacklistConfigSchema = exports_external.object({
|
|
10353
8127
|
tables: exports_external.array(exports_external.string()).default([]),
|
|
10354
8128
|
columns: exports_external.record(exports_external.array(exports_external.string())).default({})
|
|
10355
|
-
}).
|
|
8129
|
+
}).passthrough().superRefine((value, ctx) => {
|
|
8130
|
+
for (const [wrong, right] of Object.entries(BLACKLIST_KEY_ALIASES)) {
|
|
8131
|
+
if (wrong in value) {
|
|
8132
|
+
ctx.addIssue({
|
|
8133
|
+
code: exports_external.ZodIssueCode.custom,
|
|
8134
|
+
path: [wrong],
|
|
8135
|
+
message: `blacklist.${wrong} is not a setting and would be silently ignored \u2014 did you mean blacklist.${right}?`
|
|
8136
|
+
});
|
|
8137
|
+
}
|
|
8138
|
+
}
|
|
8139
|
+
}).transform((value) => ({ tables: value.tables, columns: value.columns })).optional().default({ tables: [], columns: {} });
|
|
10356
8140
|
var RedisMaskRuleSchema = exports_external.object({
|
|
10357
8141
|
keyPattern: exports_external.string().min(1),
|
|
10358
8142
|
fields: exports_external.array(exports_external.string()).optional()
|
|
@@ -10360,16 +8144,22 @@ var RedisMaskRuleSchema = exports_external.object({
|
|
|
10360
8144
|
var RedisConfigSchema = exports_external.object({
|
|
10361
8145
|
mask: exports_external.array(RedisMaskRuleSchema).default([])
|
|
10362
8146
|
}).optional();
|
|
8147
|
+
var DEFAULT_AUDIT_ROTATION = { max_bytes: 10485760, max_entries: 1e4 };
|
|
10363
8148
|
var AuditRotationConfigSchema = exports_external.object({
|
|
10364
|
-
max_bytes: exports_external.number().int().positive().default(
|
|
10365
|
-
max_entries: exports_external.number().int().positive().default(
|
|
10366
|
-
}).optional().default({
|
|
8149
|
+
max_bytes: exports_external.number().int().positive().default(DEFAULT_AUDIT_ROTATION.max_bytes),
|
|
8150
|
+
max_entries: exports_external.number().int().positive().default(DEFAULT_AUDIT_ROTATION.max_entries)
|
|
8151
|
+
}).optional().default({ ...DEFAULT_AUDIT_ROTATION });
|
|
10367
8152
|
var AuditConfigSchema = exports_external.object({
|
|
10368
8153
|
enabled: exports_external.boolean().default(true),
|
|
8154
|
+
strict: exports_external.boolean().default(false),
|
|
10369
8155
|
rotation: AuditRotationConfigSchema
|
|
8156
|
+
}).refine((audit) => !(audit.strict && !audit.enabled), {
|
|
8157
|
+
message: "audit.strict requires audit.enabled: with audit off there is nothing to write, so " + "strict would refuse nothing. Enable audit, or turn strict off.",
|
|
8158
|
+
path: ["strict"]
|
|
10370
8159
|
}).optional().default({
|
|
10371
8160
|
enabled: true,
|
|
10372
|
-
|
|
8161
|
+
strict: false,
|
|
8162
|
+
rotation: { ...DEFAULT_AUDIT_ROTATION }
|
|
10373
8163
|
});
|
|
10374
8164
|
var DbcliConfigSchema = exports_external.object({
|
|
10375
8165
|
connection: ConnectionConfigSchema,
|
|
@@ -10400,12 +8190,24 @@ var ElasticsearchNamedConnectionSchema = ElasticsearchConnectionConfigSchema.ext
|
|
|
10400
8190
|
envFile: exports_external.string().optional(),
|
|
10401
8191
|
environment: EnvironmentLabelSchema
|
|
10402
8192
|
});
|
|
10403
|
-
var
|
|
8193
|
+
var NamedConnectionUnion = exports_external.union([
|
|
10404
8194
|
SqlNamedConnectionSchema,
|
|
10405
8195
|
MongoDBNamedConnectionSchema,
|
|
10406
8196
|
RedisNamedConnectionSchema,
|
|
10407
8197
|
ElasticsearchNamedConnectionSchema
|
|
10408
8198
|
]);
|
|
8199
|
+
var NamedConnectionSchema = exports_external.preprocess((raw, ctx) => {
|
|
8200
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
8201
|
+
if ("blacklist" in raw) {
|
|
8202
|
+
ctx.addIssue({
|
|
8203
|
+
code: exports_external.ZodIssueCode.custom,
|
|
8204
|
+
path: ["blacklist"],
|
|
8205
|
+
message: "blacklist is configured once at the top level, not per connection \u2014 a blacklist here " + "would be ignored. Move it to the top-level blacklist."
|
|
8206
|
+
});
|
|
8207
|
+
}
|
|
8208
|
+
}
|
|
8209
|
+
return raw;
|
|
8210
|
+
}, NamedConnectionUnion);
|
|
10409
8211
|
var DbcliConfigV2Schema = exports_external.object({
|
|
10410
8212
|
version: exports_external.literal(2),
|
|
10411
8213
|
default: exports_external.string().min(1),
|
|
@@ -10875,7 +8677,7 @@ function migrateV1ToV2(v1) {
|
|
|
10875
8677
|
schemas: {},
|
|
10876
8678
|
metadata: v1.metadata ?? { version: "2.0" },
|
|
10877
8679
|
blacklist: v1.blacklist ?? { tables: [], columns: {} },
|
|
10878
|
-
audit: v1.audit ?? { enabled: true, rotation: {
|
|
8680
|
+
audit: v1.audit ?? { enabled: true, rotation: { ...DEFAULT_AUDIT_ROTATION } }
|
|
10879
8681
|
};
|
|
10880
8682
|
}
|
|
10881
8683
|
function upsertConnection(config, input) {
|
|
@@ -11146,7 +8948,8 @@ var DEFAULT_CONFIG = {
|
|
|
11146
8948
|
blacklist: { tables: [], columns: {} },
|
|
11147
8949
|
audit: {
|
|
11148
8950
|
enabled: true,
|
|
11149
|
-
|
|
8951
|
+
strict: false,
|
|
8952
|
+
rotation: { ...DEFAULT_AUDIT_ROTATION }
|
|
11150
8953
|
}
|
|
11151
8954
|
};
|
|
11152
8955
|
function isEnvReference(value) {
|
|
@@ -11401,6 +9204,23 @@ DBCLI_PASSWORD=${password}
|
|
|
11401
9204
|
}
|
|
11402
9205
|
};
|
|
11403
9206
|
// src/core/blacklist-manager.ts
|
|
9207
|
+
function normalizeBlacklistEntry(raw) {
|
|
9208
|
+
const trimmed = raw.trim();
|
|
9209
|
+
const quote = trimmed[0];
|
|
9210
|
+
if ((quote === '"' || quote === "`") && trimmed.length > 1 && trimmed.endsWith(quote)) {
|
|
9211
|
+
return trimmed.slice(1, -1).trim();
|
|
9212
|
+
}
|
|
9213
|
+
return trimmed;
|
|
9214
|
+
}
|
|
9215
|
+
function assertNotTableQualified(tableKey, column) {
|
|
9216
|
+
const dot = column.indexOf(".");
|
|
9217
|
+
if (dot <= 0)
|
|
9218
|
+
return;
|
|
9219
|
+
if (column.slice(0, dot).toLowerCase() !== tableKey.toLowerCase())
|
|
9220
|
+
return;
|
|
9221
|
+
throw new Error(`[BlacklistManager] blacklist.columns["${tableKey}"] entry ${JSON.stringify(column)} is ` + `qualified with its own table and would never match. Write it as ` + `${JSON.stringify(column.slice(dot + 1))}.`);
|
|
9222
|
+
}
|
|
9223
|
+
|
|
11404
9224
|
class BlacklistManager {
|
|
11405
9225
|
config;
|
|
11406
9226
|
state;
|
|
@@ -11409,6 +9229,7 @@ class BlacklistManager {
|
|
|
11409
9229
|
this.config = config;
|
|
11410
9230
|
this.overrideEnabled = (overrideEnvValue ?? Bun.env.DBCLI_OVERRIDE_BLACKLIST ?? "") === "true";
|
|
11411
9231
|
this.state = this.loadBlacklist();
|
|
9232
|
+
this.wildcardTables = [...this.state.tables].filter((entry) => /[*?[\\]/.test(entry));
|
|
11412
9233
|
}
|
|
11413
9234
|
loadBlacklist() {
|
|
11414
9235
|
const tables = new Set;
|
|
@@ -11420,7 +9241,7 @@ class BlacklistManager {
|
|
|
11420
9241
|
if (Array.isArray(blacklistConfig.tables)) {
|
|
11421
9242
|
for (const tableName of blacklistConfig.tables) {
|
|
11422
9243
|
if (typeof tableName === "string") {
|
|
11423
|
-
tables.add(tableName.toLowerCase());
|
|
9244
|
+
tables.add(normalizeBlacklistEntry(tableName).toLowerCase());
|
|
11424
9245
|
} else {
|
|
11425
9246
|
console.warn(`[BlacklistManager] Invalid table name in blacklist config: ${JSON.stringify(tableName)}`);
|
|
11426
9247
|
}
|
|
@@ -11441,13 +9262,15 @@ class BlacklistManager {
|
|
|
11441
9262
|
const columnSet = new Set;
|
|
11442
9263
|
for (const col of cols) {
|
|
11443
9264
|
if (typeof col === "string") {
|
|
11444
|
-
|
|
9265
|
+
const entry = normalizeBlacklistEntry(col);
|
|
9266
|
+
assertNotTableQualified(normalizeBlacklistEntry(tableName), entry);
|
|
9267
|
+
columnSet.add(entry);
|
|
11445
9268
|
} else {
|
|
11446
9269
|
console.warn(`[BlacklistManager] Invalid column name in blacklist.columns["${tableName}"]: ${JSON.stringify(col)}`);
|
|
11447
9270
|
}
|
|
11448
9271
|
}
|
|
11449
9272
|
if (columnSet.size > 0) {
|
|
11450
|
-
columns.set(tableName.toLowerCase(), columnSet);
|
|
9273
|
+
columns.set(normalizeBlacklistEntry(tableName).toLowerCase(), columnSet);
|
|
11451
9274
|
}
|
|
11452
9275
|
}
|
|
11453
9276
|
} else if (blacklistConfig.columns !== undefined) {
|
|
@@ -11456,17 +9279,47 @@ class BlacklistManager {
|
|
|
11456
9279
|
return { tables, columns };
|
|
11457
9280
|
}
|
|
11458
9281
|
isTableBlacklisted(tableName) {
|
|
11459
|
-
|
|
9282
|
+
const name = tableName.toLowerCase();
|
|
9283
|
+
if (this.state.tables.has(name))
|
|
9284
|
+
return true;
|
|
9285
|
+
for (const pattern of this.wildcardTables) {
|
|
9286
|
+
if (globMatches(pattern, name))
|
|
9287
|
+
return true;
|
|
9288
|
+
}
|
|
9289
|
+
return false;
|
|
9290
|
+
}
|
|
9291
|
+
wildcardTables;
|
|
9292
|
+
columnRulesFor(tableName) {
|
|
9293
|
+
const key = normalizeBlacklistEntry(tableName).toLowerCase();
|
|
9294
|
+
const direct = this.state.columns.get(key);
|
|
9295
|
+
if (direct)
|
|
9296
|
+
return direct;
|
|
9297
|
+
const dot = key.lastIndexOf(".");
|
|
9298
|
+
if (dot > 0) {
|
|
9299
|
+
const bare = this.state.columns.get(key.slice(dot + 1));
|
|
9300
|
+
if (bare)
|
|
9301
|
+
return bare;
|
|
9302
|
+
}
|
|
9303
|
+
let merged;
|
|
9304
|
+
for (const [ruleKey, columns] of this.state.columns) {
|
|
9305
|
+
const ruleDot = ruleKey.lastIndexOf(".");
|
|
9306
|
+
if (ruleDot <= 0 || ruleKey.slice(ruleDot + 1) !== key)
|
|
9307
|
+
continue;
|
|
9308
|
+
merged ??= new Set;
|
|
9309
|
+
for (const column of columns)
|
|
9310
|
+
merged.add(column);
|
|
9311
|
+
}
|
|
9312
|
+
return merged;
|
|
11460
9313
|
}
|
|
11461
9314
|
isColumnBlacklisted(tableName, columnName) {
|
|
11462
|
-
const columnSet = this.
|
|
9315
|
+
const columnSet = this.columnRulesFor(tableName);
|
|
11463
9316
|
if (!columnSet) {
|
|
11464
9317
|
return false;
|
|
11465
9318
|
}
|
|
11466
|
-
return columnSet.has(columnName);
|
|
9319
|
+
return columnSet.has(normalizeBlacklistEntry(columnName).toLowerCase());
|
|
11467
9320
|
}
|
|
11468
9321
|
getBlacklistedColumns(tableName) {
|
|
11469
|
-
const columnSet = this.
|
|
9322
|
+
const columnSet = this.columnRulesFor(tableName);
|
|
11470
9323
|
if (!columnSet) {
|
|
11471
9324
|
return [];
|
|
11472
9325
|
}
|
|
@@ -11530,11 +9383,37 @@ function expandIndexTargets(target) {
|
|
|
11530
9383
|
function matchesIndexGlob(pattern, name) {
|
|
11531
9384
|
const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern.toLowerCase();
|
|
11532
9385
|
try {
|
|
11533
|
-
return
|
|
9386
|
+
return globMatches(normalized, name.toLowerCase());
|
|
11534
9387
|
} catch {
|
|
11535
9388
|
return true;
|
|
11536
9389
|
}
|
|
11537
9390
|
}
|
|
9391
|
+
function reachesByConvention(name, entry) {
|
|
9392
|
+
const lower = name.toLowerCase();
|
|
9393
|
+
const target = entry.toLowerCase();
|
|
9394
|
+
return new RegExp(`^\\.ds-${escapeRegExp(target)}-`).test(lower) || new RegExp(`^${escapeRegExp(target)}-\\d+$`).test(lower);
|
|
9395
|
+
}
|
|
9396
|
+
function escapeRegExp(text) {
|
|
9397
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9398
|
+
}
|
|
9399
|
+
function expandBlacklistEntries(blacklisted) {
|
|
9400
|
+
const names = [];
|
|
9401
|
+
const patterns = [];
|
|
9402
|
+
for (const entry of blacklisted) {
|
|
9403
|
+
const { concrete, wildcards } = expandIndexTargets(entry);
|
|
9404
|
+
names.push(...concrete);
|
|
9405
|
+
patterns.push(...wildcards);
|
|
9406
|
+
}
|
|
9407
|
+
return { names, patterns };
|
|
9408
|
+
}
|
|
9409
|
+
function indexExpressionReaches(expression, blacklisted) {
|
|
9410
|
+
if (blacklisted.length === 0)
|
|
9411
|
+
return false;
|
|
9412
|
+
const { concrete, wildcards } = expandIndexTargets(expression);
|
|
9413
|
+
const entries = expandBlacklistEntries(blacklisted);
|
|
9414
|
+
const reachesName = (name) => entries.names.some((entry) => entry.toLowerCase() === name.toLowerCase() || reachesByConvention(name, entry)) || entries.patterns.some((pattern) => matchesIndexGlob(pattern, name));
|
|
9415
|
+
return concrete.some(reachesName) || wildcards.some((pattern) => entries.names.some((entry) => matchesIndexGlob(pattern, entry)) || entries.patterns.length > 0);
|
|
9416
|
+
}
|
|
11538
9417
|
|
|
11539
9418
|
// src/core/blacklist-validator.ts
|
|
11540
9419
|
function flattenArrayRow(row) {
|
|
@@ -11559,6 +9438,17 @@ function dedupe2(values) {
|
|
|
11559
9438
|
}
|
|
11560
9439
|
return result;
|
|
11561
9440
|
}
|
|
9441
|
+
function compileGlobRules(rules, fold, table, operation) {
|
|
9442
|
+
const globbed = rules.filter((rule) => /[*?[\\]/.test(rule));
|
|
9443
|
+
if (globbed.length === 0)
|
|
9444
|
+
return [];
|
|
9445
|
+
const { patterns, rejected } = compilePatterns(globbed.map(fold));
|
|
9446
|
+
if (rejected.length > 0) {
|
|
9447
|
+
const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
|
|
9448
|
+
throw new BlacklistError(`blacklist.columns for '${table}' has entries this matcher cannot read: ${detail}`, table, operation);
|
|
9449
|
+
}
|
|
9450
|
+
return patterns;
|
|
9451
|
+
}
|
|
11562
9452
|
|
|
11563
9453
|
class BlacklistValidator {
|
|
11564
9454
|
manager;
|
|
@@ -11592,26 +9482,23 @@ class BlacklistValidator {
|
|
|
11592
9482
|
throw new BlacklistError(message, blocked[0], operation);
|
|
11593
9483
|
}
|
|
11594
9484
|
checkIndexBlacklist(operation, target) {
|
|
11595
|
-
|
|
11596
|
-
this.checkTablesBlacklist(operation, concrete);
|
|
11597
|
-
if (wildcards.length === 0 || this.manager.canOverrideBlacklist())
|
|
9485
|
+
if (this.manager.canOverrideBlacklist())
|
|
11598
9486
|
return;
|
|
11599
9487
|
const blacklisted = Array.from(this.manager.getState().tables);
|
|
11600
9488
|
if (blacklisted.length === 0)
|
|
11601
9489
|
return;
|
|
11602
|
-
|
|
11603
|
-
if (reachable.length === 0)
|
|
9490
|
+
if (!indexExpressionReaches(target, blacklisted))
|
|
11604
9491
|
return;
|
|
11605
9492
|
const message = t_vars("errors.table_blacklisted", {
|
|
11606
|
-
table:
|
|
9493
|
+
table: target,
|
|
11607
9494
|
operation
|
|
11608
9495
|
});
|
|
11609
|
-
throw new BlacklistError(message,
|
|
9496
|
+
throw new BlacklistError(message, target, operation);
|
|
11610
9497
|
}
|
|
11611
9498
|
filterColumnsForIndexExpression(target, rows, columnList) {
|
|
11612
9499
|
const { concrete, wildcards } = expandIndexTargets(target);
|
|
11613
9500
|
const ruleKeys = Array.from(this.manager.getState().columns.keys());
|
|
11614
|
-
const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)));
|
|
9501
|
+
const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)) || concrete.some((name) => indexExpressionReaches(name, [key])));
|
|
11615
9502
|
return this.filterColumnsForTables([...concrete, ...reachable], rows, columnList);
|
|
11616
9503
|
}
|
|
11617
9504
|
checkColumnBlacklistOnWrite(tableName, fields, operation = "WRITE") {
|
|
@@ -11619,7 +9506,27 @@ class BlacklistValidator {
|
|
|
11619
9506
|
if (blacklisted.length === 0 || fields.length === 0) {
|
|
11620
9507
|
return;
|
|
11621
9508
|
}
|
|
11622
|
-
const
|
|
9509
|
+
const protectedPaths = new Set(blacklisted.map((name) => name.toLowerCase()));
|
|
9510
|
+
const globs = compileGlobRules(blacklisted, (value) => value.toLowerCase(), tableName, operation);
|
|
9511
|
+
const conflicts = fields.filter((field) => {
|
|
9512
|
+
const name = field.toLowerCase();
|
|
9513
|
+
if (protectedPaths.has(name))
|
|
9514
|
+
return true;
|
|
9515
|
+
if (matchAny(name, globs))
|
|
9516
|
+
return true;
|
|
9517
|
+
let dot = name.indexOf(".");
|
|
9518
|
+
while (dot >= 0) {
|
|
9519
|
+
if (dot > 0) {
|
|
9520
|
+
const ancestor = name.slice(0, dot);
|
|
9521
|
+
if (protectedPaths.has(ancestor))
|
|
9522
|
+
return true;
|
|
9523
|
+
if (matchAny(ancestor, globs))
|
|
9524
|
+
return true;
|
|
9525
|
+
}
|
|
9526
|
+
dot = name.indexOf(".", dot + 1);
|
|
9527
|
+
}
|
|
9528
|
+
return false;
|
|
9529
|
+
});
|
|
11623
9530
|
if (conflicts.length === 0) {
|
|
11624
9531
|
return;
|
|
11625
9532
|
}
|
|
@@ -11670,11 +9577,20 @@ class BlacklistValidator {
|
|
|
11670
9577
|
}
|
|
11671
9578
|
collect(row);
|
|
11672
9579
|
}
|
|
11673
|
-
const
|
|
9580
|
+
const foldHead = (path) => {
|
|
9581
|
+
const dot = path.indexOf(".");
|
|
9582
|
+
return dot < 0 ? path.toLowerCase() : path.slice(0, dot).toLowerCase() + path.slice(dot);
|
|
9583
|
+
};
|
|
9584
|
+
const protectedPaths = new Set(blacklistedColumns.map(foldHead));
|
|
9585
|
+
const globRules = compileGlobRules(blacklistedColumns, foldHead, tables[0] ?? "unknown", "SELECT");
|
|
9586
|
+
const presentByFolded = new Map;
|
|
9587
|
+
for (const column of presentColumns)
|
|
9588
|
+
presentByFolded.set(foldHead(column), column);
|
|
11674
9589
|
const omitted = new Set;
|
|
11675
9590
|
for (const path of blacklistedColumns) {
|
|
11676
|
-
|
|
11677
|
-
|
|
9591
|
+
const present = presentByFolded.get(foldHead(path));
|
|
9592
|
+
if (present !== undefined) {
|
|
9593
|
+
omitted.add(present);
|
|
11678
9594
|
continue;
|
|
11679
9595
|
}
|
|
11680
9596
|
const dot = path.indexOf(".");
|
|
@@ -11689,13 +9605,30 @@ class BlacklistValidator {
|
|
|
11689
9605
|
for (const column of presentColumns) {
|
|
11690
9606
|
let dot = column.indexOf(".");
|
|
11691
9607
|
while (dot >= 0) {
|
|
11692
|
-
if (dot > 0 && protectedPaths.has(column.slice(0, dot))) {
|
|
9608
|
+
if (dot > 0 && protectedPaths.has(column.slice(0, dot).toLowerCase())) {
|
|
11693
9609
|
omitted.add(column);
|
|
11694
9610
|
break;
|
|
11695
9611
|
}
|
|
11696
9612
|
dot = column.indexOf(".", dot + 1);
|
|
11697
9613
|
}
|
|
11698
9614
|
}
|
|
9615
|
+
if (globRules.length > 0) {
|
|
9616
|
+
for (const column of presentColumns) {
|
|
9617
|
+
const folded = foldHead(column);
|
|
9618
|
+
if (matchAny(folded, globRules)) {
|
|
9619
|
+
omitted.add(column);
|
|
9620
|
+
continue;
|
|
9621
|
+
}
|
|
9622
|
+
let dot = folded.indexOf(".");
|
|
9623
|
+
while (dot >= 0) {
|
|
9624
|
+
if (dot > 0 && matchAny(folded.slice(0, dot), globRules)) {
|
|
9625
|
+
omitted.add(column);
|
|
9626
|
+
break;
|
|
9627
|
+
}
|
|
9628
|
+
dot = folded.indexOf(".", dot + 1);
|
|
9629
|
+
}
|
|
9630
|
+
}
|
|
9631
|
+
}
|
|
11699
9632
|
const omittedColumns = Array.from(omitted);
|
|
11700
9633
|
if (omittedColumns.length === 0) {
|
|
11701
9634
|
return { filteredRows: rows, omittedColumns: [] };
|
|
@@ -11742,6 +9675,5 @@ export {
|
|
|
11742
9675
|
ConnectionError,
|
|
11743
9676
|
BlacklistValidator,
|
|
11744
9677
|
BlacklistManager,
|
|
11745
|
-
BlacklistError
|
|
11746
|
-
AdapterFactory
|
|
9678
|
+
BlacklistError
|
|
11747
9679
|
};
|