@carllee1983/dbcli 1.47.1 → 1.49.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 +2 -6
- package/.cursor/rules/dbcli.mdc +1 -1
- package/.cursor/skills/dbcli/reference.md +9 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/skills/dbcli/SKILL.md +1 -1
- package/.github/skills/dbcli/reference.md +9 -0
- package/CHANGELOG.md +95 -2
- package/assets/SKILL.md +1 -1
- package/assets/reference.md +9 -0
- package/assets/ui-template.html +10 -10
- package/dist/cli.mjs +1104 -128
- package/dist/core.d.ts +69 -3
- package/dist/core.mjs +632 -61
- package/gemini-extension.json +1 -1
- package/package.json +6 -4
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +2 -6
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +1 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +9 -0
- package/skills/dbcli/SKILL.md +1 -1
- package/skills/dbcli/reference.md +9 -0
package/dist/core.mjs
CHANGED
|
@@ -15819,22 +15819,27 @@ function truncateResult(command, reply, opts) {
|
|
|
15819
15819
|
return { value: reply };
|
|
15820
15820
|
}
|
|
15821
15821
|
|
|
15822
|
-
// src/
|
|
15822
|
+
// src/utils/glob.ts
|
|
15823
15823
|
function globToRegex(glob) {
|
|
15824
15824
|
let out = "^";
|
|
15825
15825
|
for (let i = 0;i < glob.length; i++) {
|
|
15826
15826
|
const c = glob[i];
|
|
15827
|
+
if (c === "\\" && i + 1 < glob.length) {
|
|
15828
|
+
out += glob[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15829
|
+
continue;
|
|
15830
|
+
}
|
|
15827
15831
|
if (c === "*")
|
|
15828
15832
|
out += ".*";
|
|
15829
15833
|
else if (c === "?")
|
|
15830
15834
|
out += ".";
|
|
15831
15835
|
else if (c === "[") {
|
|
15832
|
-
const end = glob
|
|
15833
|
-
|
|
15834
|
-
|
|
15835
|
-
|
|
15836
|
-
out += glob.slice(i, end + 1);
|
|
15836
|
+
const end = findClassEnd(glob, i);
|
|
15837
|
+
const body = end === -1 ? "" : glob.slice(i, end + 1);
|
|
15838
|
+
if (body !== "" && isValidCharacterClass(body)) {
|
|
15839
|
+
out += body;
|
|
15837
15840
|
i = end;
|
|
15841
|
+
} else {
|
|
15842
|
+
out += "\\[";
|
|
15838
15843
|
}
|
|
15839
15844
|
} else if (".^$+(){}|\\".includes(c)) {
|
|
15840
15845
|
out += "\\" + c;
|
|
@@ -15845,6 +15850,27 @@ function globToRegex(glob) {
|
|
|
15845
15850
|
out += "$";
|
|
15846
15851
|
return new RegExp(out);
|
|
15847
15852
|
}
|
|
15853
|
+
function findClassEnd(glob, open) {
|
|
15854
|
+
for (let i = open + 1;i < glob.length; i++) {
|
|
15855
|
+
if (glob[i] === "\\") {
|
|
15856
|
+
i++;
|
|
15857
|
+
continue;
|
|
15858
|
+
}
|
|
15859
|
+
if (glob[i] === "]")
|
|
15860
|
+
return i;
|
|
15861
|
+
}
|
|
15862
|
+
return -1;
|
|
15863
|
+
}
|
|
15864
|
+
function isValidCharacterClass(body) {
|
|
15865
|
+
try {
|
|
15866
|
+
new RegExp(body);
|
|
15867
|
+
return true;
|
|
15868
|
+
} catch {
|
|
15869
|
+
return false;
|
|
15870
|
+
}
|
|
15871
|
+
}
|
|
15872
|
+
|
|
15873
|
+
// src/adapters/redis/blacklist-enforcer.ts
|
|
15848
15874
|
function patternsOverlap(a, b) {
|
|
15849
15875
|
const ra = globToRegex(a);
|
|
15850
15876
|
const rb = globToRegex(b);
|
|
@@ -16801,6 +16827,32 @@ class AdapterFactory {
|
|
|
16801
16827
|
return AdapterFactory.createQueryableAdapter(options);
|
|
16802
16828
|
}
|
|
16803
16829
|
}
|
|
16830
|
+
// src/utils/sql-lexical.ts
|
|
16831
|
+
var IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
|
|
16832
|
+
var IDENTIFIER_START = /[A-Za-z_]|[\u0080-\uFFFF]/;
|
|
16833
|
+
var DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
|
|
16834
|
+
function dollarQuoteDelimiterAt(sql, index) {
|
|
16835
|
+
if (sql[index] !== "$")
|
|
16836
|
+
return;
|
|
16837
|
+
if (continuesIdentifier(sql, index))
|
|
16838
|
+
return;
|
|
16839
|
+
return sql.slice(index).match(DOLLAR_QUOTE_DELIMITER)?.[0];
|
|
16840
|
+
}
|
|
16841
|
+
function continuesIdentifier(sql, index) {
|
|
16842
|
+
let start = index;
|
|
16843
|
+
while (start > 0 && IDENTIFIER_CONTINUATION.test(sql[start - 1] ?? ""))
|
|
16844
|
+
start--;
|
|
16845
|
+
if (start === index)
|
|
16846
|
+
return false;
|
|
16847
|
+
const run = sql.slice(start, index);
|
|
16848
|
+
const first = run[0];
|
|
16849
|
+
if (IDENTIFIER_START.test(first))
|
|
16850
|
+
return true;
|
|
16851
|
+
if (first === "$")
|
|
16852
|
+
return false;
|
|
16853
|
+
return run.replace(/^[0-9]+(?:[eE][0-9]+)?/, "").length > 0;
|
|
16854
|
+
}
|
|
16855
|
+
|
|
16804
16856
|
// src/core/permission-guard.ts
|
|
16805
16857
|
class PermissionError extends Error {
|
|
16806
16858
|
classification;
|
|
@@ -16817,7 +16869,6 @@ function normalizeSQL(sql) {
|
|
|
16817
16869
|
return sql.replace(/--[^\n]*\n/g, `
|
|
16818
16870
|
`).replace(/\/\*[\s\S]*?\*\//g, " ").trim().replace(/\s+/g, " ");
|
|
16819
16871
|
}
|
|
16820
|
-
var IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
|
|
16821
16872
|
function stripCommentsAndStrings(sql, options = {}) {
|
|
16822
16873
|
let result = "";
|
|
16823
16874
|
let i = 0;
|
|
@@ -16880,8 +16931,7 @@ function stripCommentsAndStrings(sql, options = {}) {
|
|
|
16880
16931
|
continue;
|
|
16881
16932
|
}
|
|
16882
16933
|
if (options.dialect === "postgresql" && char === "$") {
|
|
16883
|
-
const
|
|
16884
|
-
const delimiter = opensToken ? sql.slice(i).match(/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/)?.[0] : undefined;
|
|
16934
|
+
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
16885
16935
|
if (delimiter) {
|
|
16886
16936
|
i += delimiter.length;
|
|
16887
16937
|
const closingIndex = sql.indexOf(delimiter, i);
|
|
@@ -17311,28 +17361,345 @@ async function getAllTablesFromAdapter(adapter) {
|
|
|
17311
17361
|
}
|
|
17312
17362
|
}
|
|
17313
17363
|
|
|
17314
|
-
// src/utils/
|
|
17315
|
-
var
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17364
|
+
// src/utils/sql-tables.ts
|
|
17365
|
+
var TABLE_INTRODUCERS = new Set([
|
|
17366
|
+
"FROM",
|
|
17367
|
+
"JOIN",
|
|
17368
|
+
"INTO",
|
|
17369
|
+
"UPDATE",
|
|
17370
|
+
"TABLE",
|
|
17371
|
+
"TRUNCATE",
|
|
17372
|
+
"COPY",
|
|
17373
|
+
"USING",
|
|
17374
|
+
"STRAIGHT_JOIN"
|
|
17375
|
+
]);
|
|
17376
|
+
var PRE_TABLE_NOISE = new Set(["ONLY", "LATERAL", "TABLE"]);
|
|
17377
|
+
var SUBQUERY_OPENERS = new Set(["SELECT", "WITH", "VALUES", "TABLE"]);
|
|
17378
|
+
var AS_KEYWORD = new Set(["AS"]);
|
|
17379
|
+
var POST_TABLE_KEYWORDS = new Set([
|
|
17380
|
+
"AS",
|
|
17381
|
+
"ON",
|
|
17382
|
+
"USING",
|
|
17383
|
+
"WHERE",
|
|
17384
|
+
"GROUP",
|
|
17385
|
+
"ORDER",
|
|
17386
|
+
"HAVING",
|
|
17387
|
+
"LIMIT",
|
|
17388
|
+
"OFFSET",
|
|
17389
|
+
"FETCH",
|
|
17390
|
+
"WINDOW",
|
|
17391
|
+
"UNION",
|
|
17392
|
+
"INTERSECT",
|
|
17393
|
+
"EXCEPT",
|
|
17394
|
+
"JOIN",
|
|
17395
|
+
"INNER",
|
|
17396
|
+
"LEFT",
|
|
17397
|
+
"RIGHT",
|
|
17398
|
+
"FULL",
|
|
17399
|
+
"OUTER",
|
|
17400
|
+
"CROSS",
|
|
17401
|
+
"NATURAL",
|
|
17402
|
+
"STRAIGHT_JOIN",
|
|
17403
|
+
"SET",
|
|
17404
|
+
"VALUES",
|
|
17405
|
+
"SELECT",
|
|
17406
|
+
"RETURNING",
|
|
17407
|
+
"FOR",
|
|
17408
|
+
"INTO",
|
|
17409
|
+
"PARTITION",
|
|
17410
|
+
"WITH",
|
|
17411
|
+
"TABLESAMPLE",
|
|
17412
|
+
"FORCE",
|
|
17413
|
+
"IGNORE",
|
|
17414
|
+
"USE"
|
|
17415
|
+
]);
|
|
17416
|
+
var RESERVED_KEYWORDS = new Set([
|
|
17417
|
+
"ALL",
|
|
17418
|
+
"AND",
|
|
17419
|
+
"AS",
|
|
17420
|
+
"ASC",
|
|
17421
|
+
"CASE",
|
|
17422
|
+
"CROSS",
|
|
17423
|
+
"DESC",
|
|
17424
|
+
"DISTINCT",
|
|
17425
|
+
"ELSE",
|
|
17426
|
+
"FALSE",
|
|
17427
|
+
"FOR",
|
|
17428
|
+
"FROM",
|
|
17429
|
+
"GROUP",
|
|
17430
|
+
"HAVING",
|
|
17431
|
+
"IN",
|
|
17432
|
+
"INNER",
|
|
17433
|
+
"INTO",
|
|
17434
|
+
"IS",
|
|
17435
|
+
"JOIN",
|
|
17436
|
+
"LEFT",
|
|
17437
|
+
"LIKE",
|
|
17438
|
+
"LIMIT",
|
|
17439
|
+
"NOT",
|
|
17440
|
+
"NULL",
|
|
17441
|
+
"ON",
|
|
17442
|
+
"OR",
|
|
17443
|
+
"ORDER",
|
|
17444
|
+
"OUTER",
|
|
17445
|
+
"RIGHT",
|
|
17446
|
+
"SELECT",
|
|
17447
|
+
"THEN",
|
|
17448
|
+
"TRUE",
|
|
17449
|
+
"UNION",
|
|
17450
|
+
"USING",
|
|
17451
|
+
"VALUES",
|
|
17452
|
+
"WHEN",
|
|
17453
|
+
"WHERE",
|
|
17454
|
+
"WITH"
|
|
17455
|
+
]);
|
|
17456
|
+
var IDENTIFIER_START2 = /[A-Za-z_\u0080-\uFFFF]/;
|
|
17457
|
+
var IDENTIFIER_PART = /[A-Za-z0-9_$\u0080-\uFFFF]/;
|
|
17458
|
+
function tokenize(sql, dialect, backslashEscapes) {
|
|
17459
|
+
const tokens = [];
|
|
17460
|
+
const mysqlDialect = dialect === "mysql" || dialect === "mariadb";
|
|
17461
|
+
let i = 0;
|
|
17462
|
+
while (i < sql.length) {
|
|
17463
|
+
const char = sql[i];
|
|
17464
|
+
const dashFollowerCode = sql.charCodeAt(i + 2);
|
|
17465
|
+
if (char === "-" && sql[i + 1] === "-" && (!mysqlDialect || sql[i + 2] === undefined || dashFollowerCode <= 32 || dashFollowerCode === 127)) {
|
|
17466
|
+
while (i < sql.length && sql[i] !== `
|
|
17467
|
+
`)
|
|
17468
|
+
i++;
|
|
17469
|
+
continue;
|
|
17470
|
+
}
|
|
17471
|
+
if (mysqlDialect && char === "#") {
|
|
17472
|
+
while (i < sql.length && sql[i] !== `
|
|
17473
|
+
`)
|
|
17474
|
+
i++;
|
|
17475
|
+
continue;
|
|
17476
|
+
}
|
|
17477
|
+
if (char === "/" && sql[i + 1] === "*") {
|
|
17478
|
+
if (mysqlDialect && (sql.startsWith("/*!", i) || sql.startsWith("/*M!", i))) {
|
|
17479
|
+
const prefixLength = sql.startsWith("/*M!", i) ? 4 : 3;
|
|
17480
|
+
const closingIndex = sql.indexOf("*/", i + prefixLength);
|
|
17481
|
+
const bodyEnd = closingIndex === -1 ? sql.length : closingIndex;
|
|
17482
|
+
const body = sql.slice(i + prefixLength, bodyEnd).replace(/^\d+/, " ");
|
|
17483
|
+
tokens.push(...tokenize(body, dialect, backslashEscapes));
|
|
17484
|
+
i = closingIndex === -1 ? sql.length : closingIndex + 2;
|
|
17485
|
+
continue;
|
|
17486
|
+
}
|
|
17487
|
+
const nests = dialect === "postgresql";
|
|
17488
|
+
let depth = 1;
|
|
17489
|
+
i += 2;
|
|
17490
|
+
while (i < sql.length && depth > 0) {
|
|
17491
|
+
if (nests && sql[i] === "/" && sql[i + 1] === "*") {
|
|
17492
|
+
depth++;
|
|
17493
|
+
i += 2;
|
|
17494
|
+
continue;
|
|
17495
|
+
}
|
|
17496
|
+
if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
17497
|
+
depth--;
|
|
17498
|
+
i += 2;
|
|
17499
|
+
continue;
|
|
17500
|
+
}
|
|
17501
|
+
i++;
|
|
17502
|
+
}
|
|
17503
|
+
continue;
|
|
17504
|
+
}
|
|
17505
|
+
if (dialect === "postgresql" && char === "$") {
|
|
17506
|
+
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
17507
|
+
if (delimiter) {
|
|
17508
|
+
i += delimiter.length;
|
|
17509
|
+
const closingIndex = sql.indexOf(delimiter, i);
|
|
17510
|
+
i = closingIndex === -1 ? sql.length : closingIndex + delimiter.length;
|
|
17511
|
+
continue;
|
|
17512
|
+
}
|
|
17513
|
+
}
|
|
17514
|
+
if (char === "'") {
|
|
17515
|
+
i++;
|
|
17516
|
+
while (i < sql.length) {
|
|
17517
|
+
if (sql[i] === "'") {
|
|
17518
|
+
if (sql[i + 1] === "'") {
|
|
17519
|
+
i += 2;
|
|
17520
|
+
continue;
|
|
17521
|
+
}
|
|
17522
|
+
i++;
|
|
17523
|
+
break;
|
|
17524
|
+
}
|
|
17525
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
17526
|
+
i += 2;
|
|
17527
|
+
continue;
|
|
17528
|
+
}
|
|
17529
|
+
i++;
|
|
17530
|
+
}
|
|
17531
|
+
continue;
|
|
17532
|
+
}
|
|
17533
|
+
if (char === '"' || char === "`") {
|
|
17534
|
+
const quote = char;
|
|
17535
|
+
i++;
|
|
17536
|
+
let value = "";
|
|
17537
|
+
while (i < sql.length) {
|
|
17538
|
+
if (sql[i] === quote) {
|
|
17539
|
+
if (sql[i + 1] === quote) {
|
|
17540
|
+
value += quote;
|
|
17541
|
+
i += 2;
|
|
17542
|
+
continue;
|
|
17543
|
+
}
|
|
17544
|
+
i++;
|
|
17545
|
+
break;
|
|
17546
|
+
}
|
|
17547
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
17548
|
+
value += sql[i + 1] ?? "";
|
|
17549
|
+
i += 2;
|
|
17550
|
+
continue;
|
|
17551
|
+
}
|
|
17552
|
+
value += sql[i];
|
|
17553
|
+
i++;
|
|
17554
|
+
}
|
|
17555
|
+
tokens.push({ value, kind: "identifier", quoted: true });
|
|
17556
|
+
continue;
|
|
17557
|
+
}
|
|
17558
|
+
if (IDENTIFIER_START2.test(char)) {
|
|
17559
|
+
let value = "";
|
|
17560
|
+
while (i < sql.length && IDENTIFIER_PART.test(sql[i])) {
|
|
17561
|
+
value += sql[i];
|
|
17562
|
+
i++;
|
|
17563
|
+
}
|
|
17564
|
+
tokens.push({ value, kind: "identifier", quoted: false });
|
|
17565
|
+
continue;
|
|
17566
|
+
}
|
|
17567
|
+
if (char === "." || char === "," || char === "(" || char === ")" || char === ";") {
|
|
17568
|
+
tokens.push({ value: char, kind: "punctuation", quoted: false });
|
|
17569
|
+
i++;
|
|
17570
|
+
continue;
|
|
17571
|
+
}
|
|
17572
|
+
i++;
|
|
17323
17573
|
}
|
|
17324
|
-
|
|
17325
|
-
|
|
17574
|
+
return tokens;
|
|
17575
|
+
}
|
|
17576
|
+
function isKeyword(token, keywords) {
|
|
17577
|
+
if (!token || token.kind !== "identifier" || token.quoted)
|
|
17578
|
+
return false;
|
|
17579
|
+
return keywords.has(token.value.toUpperCase());
|
|
17580
|
+
}
|
|
17581
|
+
function isPunctuation(token, value) {
|
|
17582
|
+
return token?.kind === "punctuation" && token.value === value;
|
|
17583
|
+
}
|
|
17584
|
+
function readQualifiedName(tokens, index) {
|
|
17585
|
+
const first = tokens[index];
|
|
17586
|
+
if (!first || first.kind !== "identifier")
|
|
17587
|
+
return null;
|
|
17588
|
+
const parts = [first.value];
|
|
17589
|
+
let cursor = index + 1;
|
|
17590
|
+
while (isPunctuation(tokens[cursor], ".") && tokens[cursor + 1]?.kind === "identifier") {
|
|
17591
|
+
parts.push(tokens[cursor + 1].value);
|
|
17592
|
+
cursor += 2;
|
|
17326
17593
|
}
|
|
17327
|
-
|
|
17328
|
-
|
|
17594
|
+
return { parts, next: cursor };
|
|
17595
|
+
}
|
|
17596
|
+
var ANY_ESCAPE_SEQUENCE = /([^0-9a-fA-F+'"\s])(?:\+([0-9a-fA-F]{6})|([0-9a-fA-F]{4}))/g;
|
|
17597
|
+
function decodedVariants(value) {
|
|
17598
|
+
const decoded = value.replace(ANY_ESCAPE_SEQUENCE, (whole, _escape, long, short) => fromCodePointOrRaw(long ?? short, whole));
|
|
17599
|
+
return decoded === value ? [] : [decoded];
|
|
17600
|
+
}
|
|
17601
|
+
function fromCodePointOrRaw(hex, whole) {
|
|
17602
|
+
const codePoint = parseInt(hex, 16);
|
|
17603
|
+
return codePoint <= 1114111 ? String.fromCodePoint(codePoint) : whole;
|
|
17604
|
+
}
|
|
17605
|
+
function extractTableReferences(sql, options = {}) {
|
|
17606
|
+
const seen = new Set;
|
|
17607
|
+
const references = [];
|
|
17608
|
+
const record = (name) => {
|
|
17609
|
+
const key = name.toLowerCase();
|
|
17610
|
+
if (name.length === 0 || seen.has(key))
|
|
17611
|
+
return;
|
|
17612
|
+
seen.add(key);
|
|
17613
|
+
references.push(name);
|
|
17614
|
+
};
|
|
17615
|
+
const dialects = options.dialect ? [options.dialect] : ["postgresql", "mysql", undefined];
|
|
17616
|
+
for (const dialect of dialects) {
|
|
17617
|
+
for (const backslashEscapes of [false, true]) {
|
|
17618
|
+
collectReferences(tokenize(sql, dialect, backslashEscapes), record);
|
|
17619
|
+
}
|
|
17620
|
+
}
|
|
17621
|
+
return references;
|
|
17622
|
+
}
|
|
17623
|
+
function collectReferences(tokens, record) {
|
|
17624
|
+
const recordName = (parts) => {
|
|
17625
|
+
const bare = parts[parts.length - 1];
|
|
17626
|
+
record(bare);
|
|
17627
|
+
for (const variant of decodedVariants(bare))
|
|
17628
|
+
record(variant);
|
|
17629
|
+
if (parts.length > 1)
|
|
17630
|
+
record(parts.join("."));
|
|
17631
|
+
};
|
|
17632
|
+
let i = 0;
|
|
17633
|
+
while (i < tokens.length) {
|
|
17634
|
+
if (!isKeyword(tokens[i], TABLE_INTRODUCERS)) {
|
|
17635
|
+
i++;
|
|
17636
|
+
continue;
|
|
17637
|
+
}
|
|
17638
|
+
const introducer = tokens[i].value.toUpperCase();
|
|
17639
|
+
const parenMeansFunction = introducer === "FROM" || introducer === "JOIN";
|
|
17640
|
+
let cursor = i + 1;
|
|
17641
|
+
while (isKeyword(tokens[cursor], PRE_TABLE_NOISE))
|
|
17642
|
+
cursor++;
|
|
17643
|
+
while (parenMeansFunction && isPunctuation(tokens[cursor], "(") && !isKeyword(tokens[cursor + 1], SUBQUERY_OPENERS) && tokens[cursor + 1]?.kind === "identifier") {
|
|
17644
|
+
cursor++;
|
|
17645
|
+
}
|
|
17646
|
+
let expectTable = true;
|
|
17647
|
+
while (expectTable) {
|
|
17648
|
+
expectTable = false;
|
|
17649
|
+
const name = readQualifiedName(tokens, cursor);
|
|
17650
|
+
if (!name)
|
|
17651
|
+
break;
|
|
17652
|
+
const isFunctionCall = parenMeansFunction && isPunctuation(tokens[name.next], "(");
|
|
17653
|
+
if (!isFunctionCall)
|
|
17654
|
+
recordName(name.parts);
|
|
17655
|
+
cursor = name.next;
|
|
17656
|
+
if (isFunctionCall)
|
|
17657
|
+
break;
|
|
17658
|
+
if (isKeyword(tokens[cursor], AS_KEYWORD))
|
|
17659
|
+
cursor++;
|
|
17660
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS)) {
|
|
17661
|
+
cursor++;
|
|
17662
|
+
}
|
|
17663
|
+
while (isPunctuation(tokens[cursor], "(")) {
|
|
17664
|
+
let depth = 0;
|
|
17665
|
+
do {
|
|
17666
|
+
if (isPunctuation(tokens[cursor], "("))
|
|
17667
|
+
depth++;
|
|
17668
|
+
else if (isPunctuation(tokens[cursor], ")"))
|
|
17669
|
+
depth--;
|
|
17670
|
+
cursor++;
|
|
17671
|
+
} while (depth > 0 && cursor < tokens.length);
|
|
17672
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS))
|
|
17673
|
+
cursor++;
|
|
17674
|
+
}
|
|
17675
|
+
if (isPunctuation(tokens[cursor], ",")) {
|
|
17676
|
+
cursor++;
|
|
17677
|
+
expectTable = true;
|
|
17678
|
+
}
|
|
17679
|
+
}
|
|
17680
|
+
i = Math.max(cursor, i + 1);
|
|
17329
17681
|
}
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17682
|
+
let index = 0;
|
|
17683
|
+
while (index < tokens.length) {
|
|
17684
|
+
const token = tokens[index];
|
|
17685
|
+
if (!token || token.kind !== "identifier") {
|
|
17686
|
+
index++;
|
|
17687
|
+
continue;
|
|
17688
|
+
}
|
|
17689
|
+
const name = readQualifiedName(tokens, index);
|
|
17690
|
+
for (let part = 0;part < name.parts.length; part++) {
|
|
17691
|
+
const value = name.parts[part];
|
|
17692
|
+
const isQuoted = tokens[index + part * 2]?.quoted === true;
|
|
17693
|
+
if (isQuoted || !RESERVED_KEYWORDS.has(value.toUpperCase())) {
|
|
17694
|
+
record(value);
|
|
17695
|
+
for (const variant of decodedVariants(value))
|
|
17696
|
+
record(variant);
|
|
17697
|
+
}
|
|
17698
|
+
}
|
|
17699
|
+
if (name.parts.length > 1)
|
|
17700
|
+
record(name.parts.join("."));
|
|
17701
|
+
index = name.next;
|
|
17334
17702
|
}
|
|
17335
|
-
return options.table || "<unknown-target>";
|
|
17336
17703
|
}
|
|
17337
17704
|
|
|
17338
17705
|
// src/core/limits.ts
|
|
@@ -22745,6 +23112,30 @@ function redactSql(sql) {
|
|
|
22745
23112
|
return redactSensitive(redacted);
|
|
22746
23113
|
}
|
|
22747
23114
|
|
|
23115
|
+
// src/utils/engine-hints.ts
|
|
23116
|
+
var TABLE_NAME_RE = /\b(?:FROM|INTO|UPDATE)\s+["'`]?([a-zA-Z_][a-zA-Z0-9_]*)["'`]?/i;
|
|
23117
|
+
function extractTableName(sql) {
|
|
23118
|
+
const match = sql.match(TABLE_NAME_RE);
|
|
23119
|
+
return match ? match[1] ?? null : null;
|
|
23120
|
+
}
|
|
23121
|
+
function getOperationTarget(system, command, options, sql) {
|
|
23122
|
+
if (system === "mongodb") {
|
|
23123
|
+
return options.collection || "<unknown-collection>";
|
|
23124
|
+
}
|
|
23125
|
+
if (system === "elasticsearch") {
|
|
23126
|
+
return options.index || options.collection || "<unknown-index>";
|
|
23127
|
+
}
|
|
23128
|
+
if (system === "redis") {
|
|
23129
|
+
return options.table || options.key || "<unknown-key>";
|
|
23130
|
+
}
|
|
23131
|
+
if (sql) {
|
|
23132
|
+
const table = extractTableName(sql);
|
|
23133
|
+
if (table)
|
|
23134
|
+
return table;
|
|
23135
|
+
}
|
|
23136
|
+
return options.table || "<unknown-target>";
|
|
23137
|
+
}
|
|
23138
|
+
|
|
22748
23139
|
// src/core/audit/integration-helper.ts
|
|
22749
23140
|
var _sessionIdService = null;
|
|
22750
23141
|
var _loggers = new Map;
|
|
@@ -22828,17 +23219,37 @@ function projectRows(rows, selection) {
|
|
|
22828
23219
|
const columnNames = collectColumnNames(projectedRows);
|
|
22829
23220
|
return { rows: normalizeRows(projectedRows, columnNames), columnNames };
|
|
22830
23221
|
}
|
|
22831
|
-
function hasFieldPath(row,
|
|
22832
|
-
return readPath(row,
|
|
23222
|
+
function hasFieldPath(row, segments) {
|
|
23223
|
+
return readPath(row, segments).found;
|
|
22833
23224
|
}
|
|
22834
23225
|
function omitFieldPaths(rows, paths) {
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
23226
|
+
const topLevel = new Set(paths);
|
|
23227
|
+
const dotted = paths.filter((path) => path.includes(".")).map((path) => ({ head: path.slice(0, path.indexOf(".")), segments: path.split(".") }));
|
|
23228
|
+
const maskRecord = (row) => {
|
|
23229
|
+
let projected = cloneRecord(row, topLevel);
|
|
23230
|
+
for (const { head, segments } of dotted) {
|
|
23231
|
+
const value = row[head];
|
|
23232
|
+
if (value !== null && typeof value === "object")
|
|
23233
|
+
projected = omitPath(projected, segments);
|
|
23234
|
+
}
|
|
22839
23235
|
return projected;
|
|
23236
|
+
};
|
|
23237
|
+
return rows.map((row) => {
|
|
23238
|
+
if (row === null || typeof row !== "object")
|
|
23239
|
+
return {};
|
|
23240
|
+
if (Array.isArray(row))
|
|
23241
|
+
return maskArrayRow(row, maskRecord);
|
|
23242
|
+
return maskRecord(row);
|
|
22840
23243
|
});
|
|
22841
23244
|
}
|
|
23245
|
+
function maskArrayRow(row, maskRecord) {
|
|
23246
|
+
const out = {};
|
|
23247
|
+
row.forEach((item, index) => {
|
|
23248
|
+
const masked = Array.isArray(item) ? maskArrayRow(item, maskRecord) : item !== null && typeof item === "object" ? maskRecord(item) : item;
|
|
23249
|
+
defineData(out, String(index), masked);
|
|
23250
|
+
});
|
|
23251
|
+
return out;
|
|
23252
|
+
}
|
|
22842
23253
|
function readPath(value, segments) {
|
|
22843
23254
|
if (segments.length === 0)
|
|
22844
23255
|
return { found: true, value };
|
|
@@ -22882,10 +23293,10 @@ function omitPath(value, segments) {
|
|
|
22882
23293
|
}
|
|
22883
23294
|
return out;
|
|
22884
23295
|
}
|
|
22885
|
-
function cloneRecord(value,
|
|
23296
|
+
function cloneRecord(value, omittedKeys) {
|
|
22886
23297
|
const out = {};
|
|
22887
23298
|
for (const [key, child] of Object.entries(value)) {
|
|
22888
|
-
if (key
|
|
23299
|
+
if (!omittedKeys.has(key))
|
|
22889
23300
|
defineData(out, key, child);
|
|
22890
23301
|
}
|
|
22891
23302
|
return out;
|
|
@@ -22978,11 +23389,9 @@ class QueryExecutor {
|
|
|
22978
23389
|
}
|
|
22979
23390
|
}
|
|
22980
23391
|
}
|
|
22981
|
-
|
|
22982
|
-
|
|
22983
|
-
|
|
22984
|
-
this.blacklistValidator.checkTableBlacklist(classification.type, tableName, []);
|
|
22985
|
-
}
|
|
23392
|
+
const referencedTables = this.blacklistValidator ? extractTableReferences(sql, { dialect: this.resolveDialect() }) : [];
|
|
23393
|
+
if (this.blacklistValidator && referencedTables.length > 0) {
|
|
23394
|
+
this.blacklistValidator.checkTablesBlacklist(classification.type, referencedTables);
|
|
22986
23395
|
}
|
|
22987
23396
|
const resultData = await this.adapter.execute(executeSql);
|
|
22988
23397
|
const executionTimeMs = Math.round(performance.now() - start);
|
|
@@ -22995,15 +23404,12 @@ class QueryExecutor {
|
|
|
22995
23404
|
let securityNotification;
|
|
22996
23405
|
let omittedColumns = [];
|
|
22997
23406
|
if (this.blacklistValidator) {
|
|
22998
|
-
const
|
|
22999
|
-
|
|
23000
|
-
|
|
23001
|
-
|
|
23002
|
-
|
|
23003
|
-
|
|
23004
|
-
columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
|
|
23005
|
-
securityNotification = this.blacklistValidator.buildSecurityNotification(tableName, filterResult.omittedColumns);
|
|
23006
|
-
}
|
|
23407
|
+
const filterResult = this.blacklistValidator.filterColumnsForTables(referencedTables, rows, columnNames);
|
|
23408
|
+
filteredRows = filterResult.filteredRows;
|
|
23409
|
+
if (filterResult.omittedColumns.length > 0) {
|
|
23410
|
+
omittedColumns = filterResult.omittedColumns;
|
|
23411
|
+
columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
|
|
23412
|
+
securityNotification = this.blacklistValidator.buildSecurityNotification(referencedTables[0] ?? "", filterResult.omittedColumns);
|
|
23007
23413
|
}
|
|
23008
23414
|
}
|
|
23009
23415
|
if (options?.fieldSelection) {
|
|
@@ -23665,6 +24071,14 @@ class BlacklistManager {
|
|
|
23665
24071
|
}
|
|
23666
24072
|
return Array.from(columnSet);
|
|
23667
24073
|
}
|
|
24074
|
+
getAllBlacklistedColumns() {
|
|
24075
|
+
const all = new Set;
|
|
24076
|
+
for (const columnSet of this.state.columns.values()) {
|
|
24077
|
+
for (const column of columnSet)
|
|
24078
|
+
all.add(column);
|
|
24079
|
+
}
|
|
24080
|
+
return Array.from(all);
|
|
24081
|
+
}
|
|
23668
24082
|
canOverrideBlacklist() {
|
|
23669
24083
|
return this.overrideEnabled;
|
|
23670
24084
|
}
|
|
@@ -23672,28 +24086,132 @@ class BlacklistManager {
|
|
|
23672
24086
|
return this.state;
|
|
23673
24087
|
}
|
|
23674
24088
|
}
|
|
24089
|
+
// src/utils/es-index-target.ts
|
|
24090
|
+
var MAX_DECODE_PASSES = 4;
|
|
24091
|
+
function unwrap(target) {
|
|
24092
|
+
let current = target;
|
|
24093
|
+
for (let pass = 0;pass < MAX_DECODE_PASSES; pass++) {
|
|
24094
|
+
let next = current;
|
|
24095
|
+
try {
|
|
24096
|
+
next = decodeURIComponent(next);
|
|
24097
|
+
} catch {}
|
|
24098
|
+
if (next.startsWith("<") && next.endsWith(">")) {
|
|
24099
|
+
next = next.slice(1, -1).replace(/\{[^}]*\}/g, "*");
|
|
24100
|
+
}
|
|
24101
|
+
if (next === current)
|
|
24102
|
+
return current;
|
|
24103
|
+
current = next;
|
|
24104
|
+
}
|
|
24105
|
+
return current;
|
|
24106
|
+
}
|
|
24107
|
+
function expandIndexTargets(target) {
|
|
24108
|
+
const concrete = [];
|
|
24109
|
+
const wildcards = [];
|
|
24110
|
+
const add = (candidate) => {
|
|
24111
|
+
if (candidate.length === 0)
|
|
24112
|
+
return;
|
|
24113
|
+
if (/[*?]/.test(candidate) || candidate.toLowerCase() === "_all")
|
|
24114
|
+
wildcards.push(candidate);
|
|
24115
|
+
else
|
|
24116
|
+
concrete.push(candidate);
|
|
24117
|
+
};
|
|
24118
|
+
for (const rawPart of unwrap(target).split(",")) {
|
|
24119
|
+
const part = unwrap(rawPart.trim()).trim().replace(/^[-+]/, "");
|
|
24120
|
+
if (part.length === 0)
|
|
24121
|
+
continue;
|
|
24122
|
+
add(part);
|
|
24123
|
+
if (part.includes(":"))
|
|
24124
|
+
for (const section of part.split(":"))
|
|
24125
|
+
add(section);
|
|
24126
|
+
}
|
|
24127
|
+
return { concrete, wildcards };
|
|
24128
|
+
}
|
|
24129
|
+
function matchesIndexGlob(pattern, name) {
|
|
24130
|
+
const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern.toLowerCase();
|
|
24131
|
+
try {
|
|
24132
|
+
return globToRegex(normalized).test(name.toLowerCase());
|
|
24133
|
+
} catch {
|
|
24134
|
+
return true;
|
|
24135
|
+
}
|
|
24136
|
+
}
|
|
24137
|
+
|
|
23675
24138
|
// src/core/blacklist-validator.ts
|
|
24139
|
+
function flattenArrayRow(row) {
|
|
24140
|
+
const records = [];
|
|
24141
|
+
for (const item of row) {
|
|
24142
|
+
if (Array.isArray(item))
|
|
24143
|
+
records.push(...flattenArrayRow(item));
|
|
24144
|
+
else if (item !== null && typeof item === "object")
|
|
24145
|
+
records.push(item);
|
|
24146
|
+
}
|
|
24147
|
+
return records;
|
|
24148
|
+
}
|
|
24149
|
+
function dedupe2(values) {
|
|
24150
|
+
const seen = new Set;
|
|
24151
|
+
const result = [];
|
|
24152
|
+
for (const value of values) {
|
|
24153
|
+
const key = value.toLowerCase();
|
|
24154
|
+
if (value.length === 0 || seen.has(key))
|
|
24155
|
+
continue;
|
|
24156
|
+
seen.add(key);
|
|
24157
|
+
result.push(value);
|
|
24158
|
+
}
|
|
24159
|
+
return result;
|
|
24160
|
+
}
|
|
24161
|
+
|
|
23676
24162
|
class BlacklistValidator {
|
|
23677
24163
|
manager;
|
|
23678
24164
|
constructor(manager) {
|
|
23679
24165
|
this.manager = manager;
|
|
23680
24166
|
}
|
|
23681
|
-
checkTableBlacklist(operation, tableName,
|
|
24167
|
+
checkTableBlacklist(operation, tableName, tableList = []) {
|
|
24168
|
+
this.checkTablesBlacklist(operation, [tableName, ...tableList]);
|
|
24169
|
+
}
|
|
24170
|
+
checkTablesBlacklist(operation, tableNames) {
|
|
24171
|
+
const tables = dedupe2(tableNames);
|
|
24172
|
+
if (tables.length === 0) {
|
|
24173
|
+
return;
|
|
24174
|
+
}
|
|
23682
24175
|
if (this.manager.canOverrideBlacklist()) {
|
|
23683
|
-
const
|
|
24176
|
+
const message2 = t_vars("warnings.blacklist_override_used", {
|
|
23684
24177
|
operation,
|
|
23685
|
-
table:
|
|
24178
|
+
table: tables.join(", ")
|
|
23686
24179
|
});
|
|
23687
|
-
console.error(
|
|
24180
|
+
console.error(message2);
|
|
23688
24181
|
return;
|
|
23689
24182
|
}
|
|
23690
|
-
|
|
23691
|
-
|
|
23692
|
-
|
|
23693
|
-
operation
|
|
23694
|
-
});
|
|
23695
|
-
throw new BlacklistError(message, tableName, operation);
|
|
24183
|
+
const blocked = tables.filter((table) => this.manager.isTableBlacklisted(table));
|
|
24184
|
+
if (blocked.length === 0) {
|
|
24185
|
+
return;
|
|
23696
24186
|
}
|
|
24187
|
+
const message = t_vars("errors.table_blacklisted", {
|
|
24188
|
+
table: blocked.join(", "),
|
|
24189
|
+
operation
|
|
24190
|
+
});
|
|
24191
|
+
throw new BlacklistError(message, blocked[0], operation);
|
|
24192
|
+
}
|
|
24193
|
+
checkIndexBlacklist(operation, target) {
|
|
24194
|
+
const { concrete, wildcards } = expandIndexTargets(target);
|
|
24195
|
+
this.checkTablesBlacklist(operation, concrete);
|
|
24196
|
+
if (wildcards.length === 0 || this.manager.canOverrideBlacklist())
|
|
24197
|
+
return;
|
|
24198
|
+
const blacklisted = Array.from(this.manager.getState().tables);
|
|
24199
|
+
if (blacklisted.length === 0)
|
|
24200
|
+
return;
|
|
24201
|
+
const reachable = wildcards.filter((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
|
|
24202
|
+
if (reachable.length === 0)
|
|
24203
|
+
return;
|
|
24204
|
+
const message = t_vars("errors.table_blacklisted", {
|
|
24205
|
+
table: reachable.join(", "),
|
|
24206
|
+
operation
|
|
24207
|
+
});
|
|
24208
|
+
throw new BlacklistError(message, reachable[0], operation);
|
|
24209
|
+
}
|
|
24210
|
+
filterColumnsForIndexExpression(target, rows, columnList) {
|
|
24211
|
+
const { concrete, wildcards } = expandIndexTargets(target);
|
|
24212
|
+
const ruleKeys = Array.from(this.manager.getState().columns.keys());
|
|
24213
|
+
const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)));
|
|
24214
|
+
return this.filterColumnsForTables([...concrete, ...reachable], rows, columnList);
|
|
23697
24215
|
}
|
|
23698
24216
|
checkColumnBlacklistOnWrite(tableName, fields, operation = "WRITE") {
|
|
23699
24217
|
const blacklisted = this.manager.getBlacklistedColumns(tableName);
|
|
@@ -23720,11 +24238,64 @@ class BlacklistValidator {
|
|
|
23720
24238
|
throw new BlacklistError(message, tableName, operation);
|
|
23721
24239
|
}
|
|
23722
24240
|
filterColumns(tableName, rows, columnList) {
|
|
23723
|
-
|
|
24241
|
+
return this.filterColumnsForTables([tableName], rows, columnList);
|
|
24242
|
+
}
|
|
24243
|
+
filterColumnsForTables(tableNames, rows, columnList) {
|
|
24244
|
+
const tables = dedupe2(tableNames);
|
|
24245
|
+
const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
|
|
23724
24246
|
if (blacklistedColumns.length === 0) {
|
|
23725
24247
|
return { filteredRows: rows, omittedColumns: [] };
|
|
23726
24248
|
}
|
|
23727
|
-
const
|
|
24249
|
+
const probeNested = blacklistedColumns.some((path3) => path3.includes("."));
|
|
24250
|
+
const presentColumns = new Set(columnList);
|
|
24251
|
+
const nestedHeads = new Set;
|
|
24252
|
+
const collect = (record) => {
|
|
24253
|
+
for (const key of Object.getOwnPropertyNames(record)) {
|
|
24254
|
+
presentColumns.add(key);
|
|
24255
|
+
if (!probeNested)
|
|
24256
|
+
continue;
|
|
24257
|
+
const value = record[key];
|
|
24258
|
+
if (value !== null && typeof value === "object")
|
|
24259
|
+
nestedHeads.add(key);
|
|
24260
|
+
}
|
|
24261
|
+
};
|
|
24262
|
+
for (const row of rows) {
|
|
24263
|
+
if (row === null || typeof row !== "object")
|
|
24264
|
+
continue;
|
|
24265
|
+
if (Array.isArray(row)) {
|
|
24266
|
+
for (const item of flattenArrayRow(row))
|
|
24267
|
+
collect(item);
|
|
24268
|
+
continue;
|
|
24269
|
+
}
|
|
24270
|
+
collect(row);
|
|
24271
|
+
}
|
|
24272
|
+
const protectedPaths = new Set(blacklistedColumns);
|
|
24273
|
+
const omitted = new Set;
|
|
24274
|
+
for (const path3 of blacklistedColumns) {
|
|
24275
|
+
if (presentColumns.has(path3)) {
|
|
24276
|
+
omitted.add(path3);
|
|
24277
|
+
continue;
|
|
24278
|
+
}
|
|
24279
|
+
const dot = path3.indexOf(".");
|
|
24280
|
+
if (dot < 0)
|
|
24281
|
+
continue;
|
|
24282
|
+
if (!nestedHeads.has(path3.slice(0, dot)))
|
|
24283
|
+
continue;
|
|
24284
|
+
const segments = path3.split(".");
|
|
24285
|
+
if (rows.some((row) => hasFieldPath(row, segments)))
|
|
24286
|
+
omitted.add(path3);
|
|
24287
|
+
}
|
|
24288
|
+
for (const column of presentColumns) {
|
|
24289
|
+
let dot = column.indexOf(".");
|
|
24290
|
+
while (dot >= 0) {
|
|
24291
|
+
if (dot > 0 && protectedPaths.has(column.slice(0, dot))) {
|
|
24292
|
+
omitted.add(column);
|
|
24293
|
+
break;
|
|
24294
|
+
}
|
|
24295
|
+
dot = column.indexOf(".", dot + 1);
|
|
24296
|
+
}
|
|
24297
|
+
}
|
|
24298
|
+
const omittedColumns = Array.from(omitted);
|
|
23728
24299
|
if (omittedColumns.length === 0) {
|
|
23729
24300
|
return { filteredRows: rows, omittedColumns: [] };
|
|
23730
24301
|
}
|