@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/cli.mjs
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@carllee1983/dbcli",
|
|
55
|
-
version: "1.
|
|
55
|
+
version: "1.49.0",
|
|
56
56
|
description: "Database CLI for AI agents",
|
|
57
57
|
type: "module",
|
|
58
58
|
publishConfig: {
|
|
@@ -99,6 +99,7 @@ var init_package = __esm(() => {
|
|
|
99
99
|
},
|
|
100
100
|
files: [
|
|
101
101
|
"dist/",
|
|
102
|
+
"!dist/.build-stamp",
|
|
102
103
|
"assets/",
|
|
103
104
|
"plugins/",
|
|
104
105
|
"skills/",
|
|
@@ -120,7 +121,7 @@ var init_package = __esm(() => {
|
|
|
120
121
|
"release:check": "bash scripts/release-check.sh",
|
|
121
122
|
"plugin:sync": "bun run scripts/sync-plugin-assets.ts --write",
|
|
122
123
|
"plugin:check": "bun run scripts/sync-plugin-assets.ts",
|
|
123
|
-
test: "bun test",
|
|
124
|
+
test: "bun test --timeout 30000",
|
|
124
125
|
"test:unit": "bun test tests/unit tests/core",
|
|
125
126
|
"test:integration": "bun test tests/integration",
|
|
126
127
|
"test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration/adapters; docker compose -f docker-compose.test.yml down",
|
|
@@ -132,8 +133,9 @@ var init_package = __esm(() => {
|
|
|
132
133
|
typecheck: "tsc --noEmit --pretty false",
|
|
133
134
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
134
135
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
135
|
-
"
|
|
136
|
-
format:
|
|
136
|
+
"format:check": "prettier --check .",
|
|
137
|
+
format: "prettier --write .",
|
|
138
|
+
"lint:fix": "eslint src tests scripts --ext .ts --fix --max-warnings=0"
|
|
137
139
|
},
|
|
138
140
|
dependencies: {
|
|
139
141
|
"cli-table3": "^0.6.5",
|
|
@@ -9316,6 +9318,14 @@ class BlacklistManager {
|
|
|
9316
9318
|
}
|
|
9317
9319
|
return Array.from(columnSet);
|
|
9318
9320
|
}
|
|
9321
|
+
getAllBlacklistedColumns() {
|
|
9322
|
+
const all = new Set;
|
|
9323
|
+
for (const columnSet of this.state.columns.values()) {
|
|
9324
|
+
for (const column of columnSet)
|
|
9325
|
+
all.add(column);
|
|
9326
|
+
}
|
|
9327
|
+
return Array.from(all);
|
|
9328
|
+
}
|
|
9319
9329
|
canOverrideBlacklist() {
|
|
9320
9330
|
return this.overrideEnabled;
|
|
9321
9331
|
}
|
|
@@ -9780,6 +9790,35 @@ var init_redis = __esm(() => {
|
|
|
9780
9790
|
};
|
|
9781
9791
|
});
|
|
9782
9792
|
|
|
9793
|
+
// src/utils/sql-lexical.ts
|
|
9794
|
+
function dollarQuoteDelimiterAt(sql, index) {
|
|
9795
|
+
if (sql[index] !== "$")
|
|
9796
|
+
return;
|
|
9797
|
+
if (continuesIdentifier(sql, index))
|
|
9798
|
+
return;
|
|
9799
|
+
return sql.slice(index).match(DOLLAR_QUOTE_DELIMITER)?.[0];
|
|
9800
|
+
}
|
|
9801
|
+
function continuesIdentifier(sql, index) {
|
|
9802
|
+
let start = index;
|
|
9803
|
+
while (start > 0 && IDENTIFIER_CONTINUATION.test(sql[start - 1] ?? ""))
|
|
9804
|
+
start--;
|
|
9805
|
+
if (start === index)
|
|
9806
|
+
return false;
|
|
9807
|
+
const run = sql.slice(start, index);
|
|
9808
|
+
const first = run[0];
|
|
9809
|
+
if (IDENTIFIER_START.test(first))
|
|
9810
|
+
return true;
|
|
9811
|
+
if (first === "$")
|
|
9812
|
+
return false;
|
|
9813
|
+
return run.replace(/^[0-9]+(?:[eE][0-9]+)?/, "").length > 0;
|
|
9814
|
+
}
|
|
9815
|
+
var IDENTIFIER_CONTINUATION, IDENTIFIER_START, DOLLAR_QUOTE_DELIMITER;
|
|
9816
|
+
var init_sql_lexical = __esm(() => {
|
|
9817
|
+
IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
|
|
9818
|
+
IDENTIFIER_START = /[A-Za-z_]|[\u0080-\uFFFF]/;
|
|
9819
|
+
DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
|
|
9820
|
+
});
|
|
9821
|
+
|
|
9783
9822
|
// src/core/permission-guard.ts
|
|
9784
9823
|
var exports_permission_guard = {};
|
|
9785
9824
|
__export(exports_permission_guard, {
|
|
@@ -9873,8 +9912,7 @@ function stripCommentsAndStrings2(sql, options = {}) {
|
|
|
9873
9912
|
continue;
|
|
9874
9913
|
}
|
|
9875
9914
|
if (options.dialect === "postgresql" && char === "$") {
|
|
9876
|
-
const
|
|
9877
|
-
const delimiter = opensToken ? sql.slice(i).match(/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/)?.[0] : undefined;
|
|
9915
|
+
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
9878
9916
|
if (delimiter) {
|
|
9879
9917
|
i += delimiter.length;
|
|
9880
9918
|
const closingIndex = sql.indexOf(delimiter, i);
|
|
@@ -10348,8 +10386,9 @@ function checkElasticsearchPermission(classification, permission) {
|
|
|
10348
10386
|
reason: `Elasticsearch ${classification.type} operation requires higher permission tier`
|
|
10349
10387
|
};
|
|
10350
10388
|
}
|
|
10351
|
-
var PermissionError,
|
|
10389
|
+
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, REDIS_COMMAND_PERMISSION, PERMISSION_RANK;
|
|
10352
10390
|
var init_permission_guard = __esm(() => {
|
|
10391
|
+
init_sql_lexical();
|
|
10353
10392
|
PermissionError = class PermissionError extends Error {
|
|
10354
10393
|
classification;
|
|
10355
10394
|
requiredPermission;
|
|
@@ -10361,7 +10400,6 @@ var init_permission_guard = __esm(() => {
|
|
|
10361
10400
|
Object.setPrototypeOf(this, PermissionError.prototype);
|
|
10362
10401
|
}
|
|
10363
10402
|
};
|
|
10364
|
-
IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
|
|
10365
10403
|
SQL_DIALECTS = ["postgresql", "mysql", "mariadb"];
|
|
10366
10404
|
SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
|
|
10367
10405
|
SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
|
|
@@ -26063,22 +26101,27 @@ var init_size_guard2 = __esm(() => {
|
|
|
26063
26101
|
init_types4();
|
|
26064
26102
|
});
|
|
26065
26103
|
|
|
26066
|
-
// src/
|
|
26104
|
+
// src/utils/glob.ts
|
|
26067
26105
|
function globToRegex(glob) {
|
|
26068
26106
|
let out = "^";
|
|
26069
26107
|
for (let i = 0;i < glob.length; i++) {
|
|
26070
26108
|
const c2 = glob[i];
|
|
26109
|
+
if (c2 === "\\" && i + 1 < glob.length) {
|
|
26110
|
+
out += glob[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26111
|
+
continue;
|
|
26112
|
+
}
|
|
26071
26113
|
if (c2 === "*")
|
|
26072
26114
|
out += ".*";
|
|
26073
26115
|
else if (c2 === "?")
|
|
26074
26116
|
out += ".";
|
|
26075
26117
|
else if (c2 === "[") {
|
|
26076
|
-
const end = glob
|
|
26077
|
-
|
|
26078
|
-
|
|
26079
|
-
|
|
26080
|
-
out += glob.slice(i, end + 1);
|
|
26118
|
+
const end = findClassEnd(glob, i);
|
|
26119
|
+
const body = end === -1 ? "" : glob.slice(i, end + 1);
|
|
26120
|
+
if (body !== "" && isValidCharacterClass(body)) {
|
|
26121
|
+
out += body;
|
|
26081
26122
|
i = end;
|
|
26123
|
+
} else {
|
|
26124
|
+
out += "\\[";
|
|
26082
26125
|
}
|
|
26083
26126
|
} else if (".^$+(){}|\\".includes(c2)) {
|
|
26084
26127
|
out += "\\" + c2;
|
|
@@ -26089,6 +26132,27 @@ function globToRegex(glob) {
|
|
|
26089
26132
|
out += "$";
|
|
26090
26133
|
return new RegExp(out);
|
|
26091
26134
|
}
|
|
26135
|
+
function findClassEnd(glob, open2) {
|
|
26136
|
+
for (let i = open2 + 1;i < glob.length; i++) {
|
|
26137
|
+
if (glob[i] === "\\") {
|
|
26138
|
+
i++;
|
|
26139
|
+
continue;
|
|
26140
|
+
}
|
|
26141
|
+
if (glob[i] === "]")
|
|
26142
|
+
return i;
|
|
26143
|
+
}
|
|
26144
|
+
return -1;
|
|
26145
|
+
}
|
|
26146
|
+
function isValidCharacterClass(body) {
|
|
26147
|
+
try {
|
|
26148
|
+
new RegExp(body);
|
|
26149
|
+
return true;
|
|
26150
|
+
} catch {
|
|
26151
|
+
return false;
|
|
26152
|
+
}
|
|
26153
|
+
}
|
|
26154
|
+
|
|
26155
|
+
// src/adapters/redis/blacklist-enforcer.ts
|
|
26092
26156
|
function patternsOverlap(a, b) {
|
|
26093
26157
|
const ra = globToRegex(a);
|
|
26094
26158
|
const rb = globToRegex(b);
|
|
@@ -30248,16 +30312,36 @@ function projectRows(rows, selection) {
|
|
|
30248
30312
|
const columnNames = collectColumnNames(projectedRows);
|
|
30249
30313
|
return { rows: normalizeRows(projectedRows, columnNames), columnNames };
|
|
30250
30314
|
}
|
|
30251
|
-
function hasFieldPath(row,
|
|
30252
|
-
return readPath(row,
|
|
30315
|
+
function hasFieldPath(row, segments) {
|
|
30316
|
+
return readPath(row, segments).found;
|
|
30253
30317
|
}
|
|
30254
30318
|
function omitFieldPaths(rows, paths) {
|
|
30255
|
-
|
|
30256
|
-
|
|
30257
|
-
|
|
30258
|
-
|
|
30319
|
+
const topLevel = new Set(paths);
|
|
30320
|
+
const dotted = paths.filter((path5) => path5.includes(".")).map((path5) => ({ head: path5.slice(0, path5.indexOf(".")), segments: path5.split(".") }));
|
|
30321
|
+
const maskRecord = (row) => {
|
|
30322
|
+
let projected = cloneRecord(row, topLevel);
|
|
30323
|
+
for (const { head, segments } of dotted) {
|
|
30324
|
+
const value = row[head];
|
|
30325
|
+
if (value !== null && typeof value === "object")
|
|
30326
|
+
projected = omitPath(projected, segments);
|
|
30327
|
+
}
|
|
30259
30328
|
return projected;
|
|
30329
|
+
};
|
|
30330
|
+
return rows.map((row) => {
|
|
30331
|
+
if (row === null || typeof row !== "object")
|
|
30332
|
+
return {};
|
|
30333
|
+
if (Array.isArray(row))
|
|
30334
|
+
return maskArrayRow(row, maskRecord);
|
|
30335
|
+
return maskRecord(row);
|
|
30336
|
+
});
|
|
30337
|
+
}
|
|
30338
|
+
function maskArrayRow(row, maskRecord) {
|
|
30339
|
+
const out = {};
|
|
30340
|
+
row.forEach((item, index) => {
|
|
30341
|
+
const masked = Array.isArray(item) ? maskArrayRow(item, maskRecord) : item !== null && typeof item === "object" ? maskRecord(item) : item;
|
|
30342
|
+
defineData(out, String(index), masked);
|
|
30260
30343
|
});
|
|
30344
|
+
return out;
|
|
30261
30345
|
}
|
|
30262
30346
|
function toMongoProjection(selection) {
|
|
30263
30347
|
const projection = {};
|
|
@@ -30321,10 +30405,10 @@ function omitPath(value, segments) {
|
|
|
30321
30405
|
}
|
|
30322
30406
|
return out;
|
|
30323
30407
|
}
|
|
30324
|
-
function cloneRecord(value,
|
|
30408
|
+
function cloneRecord(value, omittedKeys) {
|
|
30325
30409
|
const out = {};
|
|
30326
30410
|
for (const [key, child] of Object.entries(value)) {
|
|
30327
|
-
if (key
|
|
30411
|
+
if (!omittedKeys.has(key))
|
|
30328
30412
|
defineData(out, key, child);
|
|
30329
30413
|
}
|
|
30330
30414
|
return out;
|
|
@@ -30374,28 +30458,171 @@ var init_field_projection = __esm(() => {
|
|
|
30374
30458
|
UNSAFE_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
30375
30459
|
});
|
|
30376
30460
|
|
|
30461
|
+
// src/utils/es-index-target.ts
|
|
30462
|
+
function normalizeEsPath(path5) {
|
|
30463
|
+
let current = path5;
|
|
30464
|
+
for (let pass = 0;pass < MAX_DECODE_PASSES; pass++) {
|
|
30465
|
+
let decoded = current;
|
|
30466
|
+
try {
|
|
30467
|
+
decoded = decodeURIComponent(current);
|
|
30468
|
+
} catch {}
|
|
30469
|
+
const segments = [];
|
|
30470
|
+
for (const segment of decoded.split("/")) {
|
|
30471
|
+
if (segment === "." || segment === "")
|
|
30472
|
+
continue;
|
|
30473
|
+
if (segment === "..") {
|
|
30474
|
+
segments.pop();
|
|
30475
|
+
continue;
|
|
30476
|
+
}
|
|
30477
|
+
segments.push(segment);
|
|
30478
|
+
}
|
|
30479
|
+
const resolved = `/${segments.join("/")}`;
|
|
30480
|
+
if (resolved === current)
|
|
30481
|
+
return resolved;
|
|
30482
|
+
current = resolved;
|
|
30483
|
+
}
|
|
30484
|
+
return current;
|
|
30485
|
+
}
|
|
30486
|
+
function unwrap(target) {
|
|
30487
|
+
let current = target;
|
|
30488
|
+
for (let pass = 0;pass < MAX_DECODE_PASSES; pass++) {
|
|
30489
|
+
let next = current;
|
|
30490
|
+
try {
|
|
30491
|
+
next = decodeURIComponent(next);
|
|
30492
|
+
} catch {}
|
|
30493
|
+
if (next.startsWith("<") && next.endsWith(">")) {
|
|
30494
|
+
next = next.slice(1, -1).replace(/\{[^}]*\}/g, "*");
|
|
30495
|
+
}
|
|
30496
|
+
if (next === current)
|
|
30497
|
+
return current;
|
|
30498
|
+
current = next;
|
|
30499
|
+
}
|
|
30500
|
+
return current;
|
|
30501
|
+
}
|
|
30502
|
+
function expandIndexTargets(target) {
|
|
30503
|
+
const concrete = [];
|
|
30504
|
+
const wildcards = [];
|
|
30505
|
+
const add = (candidate) => {
|
|
30506
|
+
if (candidate.length === 0)
|
|
30507
|
+
return;
|
|
30508
|
+
if (/[*?]/.test(candidate) || candidate.toLowerCase() === "_all")
|
|
30509
|
+
wildcards.push(candidate);
|
|
30510
|
+
else
|
|
30511
|
+
concrete.push(candidate);
|
|
30512
|
+
};
|
|
30513
|
+
for (const rawPart of unwrap(target).split(",")) {
|
|
30514
|
+
const part = unwrap(rawPart.trim()).trim().replace(/^[-+]/, "");
|
|
30515
|
+
if (part.length === 0)
|
|
30516
|
+
continue;
|
|
30517
|
+
add(part);
|
|
30518
|
+
if (part.includes(":"))
|
|
30519
|
+
for (const section of part.split(":"))
|
|
30520
|
+
add(section);
|
|
30521
|
+
}
|
|
30522
|
+
return { concrete, wildcards };
|
|
30523
|
+
}
|
|
30524
|
+
function matchesIndexGlob(pattern, name2) {
|
|
30525
|
+
const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern.toLowerCase();
|
|
30526
|
+
try {
|
|
30527
|
+
return globToRegex(normalized).test(name2.toLowerCase());
|
|
30528
|
+
} catch {
|
|
30529
|
+
return true;
|
|
30530
|
+
}
|
|
30531
|
+
}
|
|
30532
|
+
function reachesByConvention(name2, entry) {
|
|
30533
|
+
const lower = name2.toLowerCase();
|
|
30534
|
+
const target = entry.toLowerCase();
|
|
30535
|
+
return new RegExp(`^\\.ds-${escapeRegExp(target)}-`).test(lower) || new RegExp(`^${escapeRegExp(target)}-\\d+$`).test(lower);
|
|
30536
|
+
}
|
|
30537
|
+
function escapeRegExp(text2) {
|
|
30538
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30539
|
+
}
|
|
30540
|
+
function indexExpressionReaches(expression, blacklisted) {
|
|
30541
|
+
if (blacklisted.length === 0)
|
|
30542
|
+
return false;
|
|
30543
|
+
const { concrete, wildcards } = expandIndexTargets(expression);
|
|
30544
|
+
return concrete.some((name2) => blacklisted.some((entry) => entry.toLowerCase() === name2.toLowerCase() || reachesByConvention(name2, entry))) || wildcards.some((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
|
|
30545
|
+
}
|
|
30546
|
+
var MAX_DECODE_PASSES = 4;
|
|
30547
|
+
var init_es_index_target = () => {};
|
|
30548
|
+
|
|
30377
30549
|
// src/core/blacklist-validator.ts
|
|
30550
|
+
function flattenArrayRow(row) {
|
|
30551
|
+
const records = [];
|
|
30552
|
+
for (const item of row) {
|
|
30553
|
+
if (Array.isArray(item))
|
|
30554
|
+
records.push(...flattenArrayRow(item));
|
|
30555
|
+
else if (item !== null && typeof item === "object")
|
|
30556
|
+
records.push(item);
|
|
30557
|
+
}
|
|
30558
|
+
return records;
|
|
30559
|
+
}
|
|
30560
|
+
function dedupe2(values) {
|
|
30561
|
+
const seen = new Set;
|
|
30562
|
+
const result = [];
|
|
30563
|
+
for (const value of values) {
|
|
30564
|
+
const key = value.toLowerCase();
|
|
30565
|
+
if (value.length === 0 || seen.has(key))
|
|
30566
|
+
continue;
|
|
30567
|
+
seen.add(key);
|
|
30568
|
+
result.push(value);
|
|
30569
|
+
}
|
|
30570
|
+
return result;
|
|
30571
|
+
}
|
|
30572
|
+
|
|
30378
30573
|
class BlacklistValidator {
|
|
30379
30574
|
manager;
|
|
30380
30575
|
constructor(manager) {
|
|
30381
30576
|
this.manager = manager;
|
|
30382
30577
|
}
|
|
30383
|
-
checkTableBlacklist(operation, tableName,
|
|
30578
|
+
checkTableBlacklist(operation, tableName, tableList = []) {
|
|
30579
|
+
this.checkTablesBlacklist(operation, [tableName, ...tableList]);
|
|
30580
|
+
}
|
|
30581
|
+
checkTablesBlacklist(operation, tableNames) {
|
|
30582
|
+
const tables = dedupe2(tableNames);
|
|
30583
|
+
if (tables.length === 0) {
|
|
30584
|
+
return;
|
|
30585
|
+
}
|
|
30384
30586
|
if (this.manager.canOverrideBlacklist()) {
|
|
30385
|
-
const
|
|
30587
|
+
const message2 = t_vars("warnings.blacklist_override_used", {
|
|
30386
30588
|
operation,
|
|
30387
|
-
table:
|
|
30589
|
+
table: tables.join(", ")
|
|
30388
30590
|
});
|
|
30389
|
-
console.error(
|
|
30591
|
+
console.error(message2);
|
|
30390
30592
|
return;
|
|
30391
30593
|
}
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
operation
|
|
30396
|
-
});
|
|
30397
|
-
throw new BlacklistError(message, tableName, operation);
|
|
30594
|
+
const blocked = tables.filter((table) => this.manager.isTableBlacklisted(table));
|
|
30595
|
+
if (blocked.length === 0) {
|
|
30596
|
+
return;
|
|
30398
30597
|
}
|
|
30598
|
+
const message = t_vars("errors.table_blacklisted", {
|
|
30599
|
+
table: blocked.join(", "),
|
|
30600
|
+
operation
|
|
30601
|
+
});
|
|
30602
|
+
throw new BlacklistError(message, blocked[0], operation);
|
|
30603
|
+
}
|
|
30604
|
+
checkIndexBlacklist(operation, target) {
|
|
30605
|
+
const { concrete, wildcards } = expandIndexTargets(target);
|
|
30606
|
+
this.checkTablesBlacklist(operation, concrete);
|
|
30607
|
+
if (wildcards.length === 0 || this.manager.canOverrideBlacklist())
|
|
30608
|
+
return;
|
|
30609
|
+
const blacklisted = Array.from(this.manager.getState().tables);
|
|
30610
|
+
if (blacklisted.length === 0)
|
|
30611
|
+
return;
|
|
30612
|
+
const reachable = wildcards.filter((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
|
|
30613
|
+
if (reachable.length === 0)
|
|
30614
|
+
return;
|
|
30615
|
+
const message = t_vars("errors.table_blacklisted", {
|
|
30616
|
+
table: reachable.join(", "),
|
|
30617
|
+
operation
|
|
30618
|
+
});
|
|
30619
|
+
throw new BlacklistError(message, reachable[0], operation);
|
|
30620
|
+
}
|
|
30621
|
+
filterColumnsForIndexExpression(target, rows, columnList) {
|
|
30622
|
+
const { concrete, wildcards } = expandIndexTargets(target);
|
|
30623
|
+
const ruleKeys = Array.from(this.manager.getState().columns.keys());
|
|
30624
|
+
const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)));
|
|
30625
|
+
return this.filterColumnsForTables([...concrete, ...reachable], rows, columnList);
|
|
30399
30626
|
}
|
|
30400
30627
|
checkColumnBlacklistOnWrite(tableName, fields, operation = "WRITE") {
|
|
30401
30628
|
const blacklisted = this.manager.getBlacklistedColumns(tableName);
|
|
@@ -30422,11 +30649,64 @@ class BlacklistValidator {
|
|
|
30422
30649
|
throw new BlacklistError(message, tableName, operation);
|
|
30423
30650
|
}
|
|
30424
30651
|
filterColumns(tableName, rows, columnList) {
|
|
30425
|
-
|
|
30652
|
+
return this.filterColumnsForTables([tableName], rows, columnList);
|
|
30653
|
+
}
|
|
30654
|
+
filterColumnsForTables(tableNames, rows, columnList) {
|
|
30655
|
+
const tables = dedupe2(tableNames);
|
|
30656
|
+
const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
|
|
30426
30657
|
if (blacklistedColumns.length === 0) {
|
|
30427
30658
|
return { filteredRows: rows, omittedColumns: [] };
|
|
30428
30659
|
}
|
|
30429
|
-
const
|
|
30660
|
+
const probeNested = blacklistedColumns.some((path5) => path5.includes("."));
|
|
30661
|
+
const presentColumns = new Set(columnList);
|
|
30662
|
+
const nestedHeads = new Set;
|
|
30663
|
+
const collect2 = (record) => {
|
|
30664
|
+
for (const key of Object.getOwnPropertyNames(record)) {
|
|
30665
|
+
presentColumns.add(key);
|
|
30666
|
+
if (!probeNested)
|
|
30667
|
+
continue;
|
|
30668
|
+
const value = record[key];
|
|
30669
|
+
if (value !== null && typeof value === "object")
|
|
30670
|
+
nestedHeads.add(key);
|
|
30671
|
+
}
|
|
30672
|
+
};
|
|
30673
|
+
for (const row of rows) {
|
|
30674
|
+
if (row === null || typeof row !== "object")
|
|
30675
|
+
continue;
|
|
30676
|
+
if (Array.isArray(row)) {
|
|
30677
|
+
for (const item of flattenArrayRow(row))
|
|
30678
|
+
collect2(item);
|
|
30679
|
+
continue;
|
|
30680
|
+
}
|
|
30681
|
+
collect2(row);
|
|
30682
|
+
}
|
|
30683
|
+
const protectedPaths = new Set(blacklistedColumns);
|
|
30684
|
+
const omitted = new Set;
|
|
30685
|
+
for (const path5 of blacklistedColumns) {
|
|
30686
|
+
if (presentColumns.has(path5)) {
|
|
30687
|
+
omitted.add(path5);
|
|
30688
|
+
continue;
|
|
30689
|
+
}
|
|
30690
|
+
const dot = path5.indexOf(".");
|
|
30691
|
+
if (dot < 0)
|
|
30692
|
+
continue;
|
|
30693
|
+
if (!nestedHeads.has(path5.slice(0, dot)))
|
|
30694
|
+
continue;
|
|
30695
|
+
const segments = path5.split(".");
|
|
30696
|
+
if (rows.some((row) => hasFieldPath(row, segments)))
|
|
30697
|
+
omitted.add(path5);
|
|
30698
|
+
}
|
|
30699
|
+
for (const column of presentColumns) {
|
|
30700
|
+
let dot = column.indexOf(".");
|
|
30701
|
+
while (dot >= 0) {
|
|
30702
|
+
if (dot > 0 && protectedPaths.has(column.slice(0, dot))) {
|
|
30703
|
+
omitted.add(column);
|
|
30704
|
+
break;
|
|
30705
|
+
}
|
|
30706
|
+
dot = column.indexOf(".", dot + 1);
|
|
30707
|
+
}
|
|
30708
|
+
}
|
|
30709
|
+
const omittedColumns = Array.from(omitted);
|
|
30430
30710
|
if (omittedColumns.length === 0) {
|
|
30431
30711
|
return { filteredRows: rows, omittedColumns: [] };
|
|
30432
30712
|
}
|
|
@@ -30446,6 +30726,7 @@ var init_blacklist_validator = __esm(() => {
|
|
|
30446
30726
|
init_blacklist();
|
|
30447
30727
|
init_message_loader();
|
|
30448
30728
|
init_field_projection();
|
|
30729
|
+
init_es_index_target();
|
|
30449
30730
|
});
|
|
30450
30731
|
|
|
30451
30732
|
// src/core/verification/types.ts
|
|
@@ -34433,6 +34714,355 @@ async function openInBrowser(target) {
|
|
|
34433
34714
|
}
|
|
34434
34715
|
var init_opener = () => {};
|
|
34435
34716
|
|
|
34717
|
+
// src/utils/sql-tables.ts
|
|
34718
|
+
var exports_sql_tables = {};
|
|
34719
|
+
__export(exports_sql_tables, {
|
|
34720
|
+
extractTableReferences: () => extractTableReferences
|
|
34721
|
+
});
|
|
34722
|
+
function tokenize(sql, dialect, backslashEscapes) {
|
|
34723
|
+
const tokens = [];
|
|
34724
|
+
const mysqlDialect = dialect === "mysql" || dialect === "mariadb";
|
|
34725
|
+
let i = 0;
|
|
34726
|
+
while (i < sql.length) {
|
|
34727
|
+
const char = sql[i];
|
|
34728
|
+
const dashFollowerCode = sql.charCodeAt(i + 2);
|
|
34729
|
+
if (char === "-" && sql[i + 1] === "-" && (!mysqlDialect || sql[i + 2] === undefined || dashFollowerCode <= 32 || dashFollowerCode === 127)) {
|
|
34730
|
+
while (i < sql.length && sql[i] !== `
|
|
34731
|
+
`)
|
|
34732
|
+
i++;
|
|
34733
|
+
continue;
|
|
34734
|
+
}
|
|
34735
|
+
if (mysqlDialect && char === "#") {
|
|
34736
|
+
while (i < sql.length && sql[i] !== `
|
|
34737
|
+
`)
|
|
34738
|
+
i++;
|
|
34739
|
+
continue;
|
|
34740
|
+
}
|
|
34741
|
+
if (char === "/" && sql[i + 1] === "*") {
|
|
34742
|
+
if (mysqlDialect && (sql.startsWith("/*!", i) || sql.startsWith("/*M!", i))) {
|
|
34743
|
+
const prefixLength = sql.startsWith("/*M!", i) ? 4 : 3;
|
|
34744
|
+
const closingIndex = sql.indexOf("*/", i + prefixLength);
|
|
34745
|
+
const bodyEnd = closingIndex === -1 ? sql.length : closingIndex;
|
|
34746
|
+
const body = sql.slice(i + prefixLength, bodyEnd).replace(/^\d+/, " ");
|
|
34747
|
+
tokens.push(...tokenize(body, dialect, backslashEscapes));
|
|
34748
|
+
i = closingIndex === -1 ? sql.length : closingIndex + 2;
|
|
34749
|
+
continue;
|
|
34750
|
+
}
|
|
34751
|
+
const nests = dialect === "postgresql";
|
|
34752
|
+
let depth = 1;
|
|
34753
|
+
i += 2;
|
|
34754
|
+
while (i < sql.length && depth > 0) {
|
|
34755
|
+
if (nests && sql[i] === "/" && sql[i + 1] === "*") {
|
|
34756
|
+
depth++;
|
|
34757
|
+
i += 2;
|
|
34758
|
+
continue;
|
|
34759
|
+
}
|
|
34760
|
+
if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
34761
|
+
depth--;
|
|
34762
|
+
i += 2;
|
|
34763
|
+
continue;
|
|
34764
|
+
}
|
|
34765
|
+
i++;
|
|
34766
|
+
}
|
|
34767
|
+
continue;
|
|
34768
|
+
}
|
|
34769
|
+
if (dialect === "postgresql" && char === "$") {
|
|
34770
|
+
const delimiter = dollarQuoteDelimiterAt(sql, i);
|
|
34771
|
+
if (delimiter) {
|
|
34772
|
+
i += delimiter.length;
|
|
34773
|
+
const closingIndex = sql.indexOf(delimiter, i);
|
|
34774
|
+
i = closingIndex === -1 ? sql.length : closingIndex + delimiter.length;
|
|
34775
|
+
continue;
|
|
34776
|
+
}
|
|
34777
|
+
}
|
|
34778
|
+
if (char === "'") {
|
|
34779
|
+
i++;
|
|
34780
|
+
while (i < sql.length) {
|
|
34781
|
+
if (sql[i] === "'") {
|
|
34782
|
+
if (sql[i + 1] === "'") {
|
|
34783
|
+
i += 2;
|
|
34784
|
+
continue;
|
|
34785
|
+
}
|
|
34786
|
+
i++;
|
|
34787
|
+
break;
|
|
34788
|
+
}
|
|
34789
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
34790
|
+
i += 2;
|
|
34791
|
+
continue;
|
|
34792
|
+
}
|
|
34793
|
+
i++;
|
|
34794
|
+
}
|
|
34795
|
+
continue;
|
|
34796
|
+
}
|
|
34797
|
+
if (char === '"' || char === "`") {
|
|
34798
|
+
const quote = char;
|
|
34799
|
+
i++;
|
|
34800
|
+
let value = "";
|
|
34801
|
+
while (i < sql.length) {
|
|
34802
|
+
if (sql[i] === quote) {
|
|
34803
|
+
if (sql[i + 1] === quote) {
|
|
34804
|
+
value += quote;
|
|
34805
|
+
i += 2;
|
|
34806
|
+
continue;
|
|
34807
|
+
}
|
|
34808
|
+
i++;
|
|
34809
|
+
break;
|
|
34810
|
+
}
|
|
34811
|
+
if (backslashEscapes && sql[i] === "\\") {
|
|
34812
|
+
value += sql[i + 1] ?? "";
|
|
34813
|
+
i += 2;
|
|
34814
|
+
continue;
|
|
34815
|
+
}
|
|
34816
|
+
value += sql[i];
|
|
34817
|
+
i++;
|
|
34818
|
+
}
|
|
34819
|
+
tokens.push({ value, kind: "identifier", quoted: true });
|
|
34820
|
+
continue;
|
|
34821
|
+
}
|
|
34822
|
+
if (IDENTIFIER_START2.test(char)) {
|
|
34823
|
+
let value = "";
|
|
34824
|
+
while (i < sql.length && IDENTIFIER_PART.test(sql[i])) {
|
|
34825
|
+
value += sql[i];
|
|
34826
|
+
i++;
|
|
34827
|
+
}
|
|
34828
|
+
tokens.push({ value, kind: "identifier", quoted: false });
|
|
34829
|
+
continue;
|
|
34830
|
+
}
|
|
34831
|
+
if (char === "." || char === "," || char === "(" || char === ")" || char === ";") {
|
|
34832
|
+
tokens.push({ value: char, kind: "punctuation", quoted: false });
|
|
34833
|
+
i++;
|
|
34834
|
+
continue;
|
|
34835
|
+
}
|
|
34836
|
+
i++;
|
|
34837
|
+
}
|
|
34838
|
+
return tokens;
|
|
34839
|
+
}
|
|
34840
|
+
function isKeyword(token, keywords) {
|
|
34841
|
+
if (!token || token.kind !== "identifier" || token.quoted)
|
|
34842
|
+
return false;
|
|
34843
|
+
return keywords.has(token.value.toUpperCase());
|
|
34844
|
+
}
|
|
34845
|
+
function isPunctuation(token, value) {
|
|
34846
|
+
return token?.kind === "punctuation" && token.value === value;
|
|
34847
|
+
}
|
|
34848
|
+
function readQualifiedName(tokens, index) {
|
|
34849
|
+
const first = tokens[index];
|
|
34850
|
+
if (!first || first.kind !== "identifier")
|
|
34851
|
+
return null;
|
|
34852
|
+
const parts = [first.value];
|
|
34853
|
+
let cursor = index + 1;
|
|
34854
|
+
while (isPunctuation(tokens[cursor], ".") && tokens[cursor + 1]?.kind === "identifier") {
|
|
34855
|
+
parts.push(tokens[cursor + 1].value);
|
|
34856
|
+
cursor += 2;
|
|
34857
|
+
}
|
|
34858
|
+
return { parts, next: cursor };
|
|
34859
|
+
}
|
|
34860
|
+
function decodedVariants(value) {
|
|
34861
|
+
const decoded = value.replace(ANY_ESCAPE_SEQUENCE, (whole, _escape, long, short) => fromCodePointOrRaw(long ?? short, whole));
|
|
34862
|
+
return decoded === value ? [] : [decoded];
|
|
34863
|
+
}
|
|
34864
|
+
function fromCodePointOrRaw(hex, whole) {
|
|
34865
|
+
const codePoint = parseInt(hex, 16);
|
|
34866
|
+
return codePoint <= 1114111 ? String.fromCodePoint(codePoint) : whole;
|
|
34867
|
+
}
|
|
34868
|
+
function extractTableReferences(sql, options = {}) {
|
|
34869
|
+
const seen = new Set;
|
|
34870
|
+
const references = [];
|
|
34871
|
+
const record = (name2) => {
|
|
34872
|
+
const key = name2.toLowerCase();
|
|
34873
|
+
if (name2.length === 0 || seen.has(key))
|
|
34874
|
+
return;
|
|
34875
|
+
seen.add(key);
|
|
34876
|
+
references.push(name2);
|
|
34877
|
+
};
|
|
34878
|
+
const dialects = options.dialect ? [options.dialect] : ["postgresql", "mysql", undefined];
|
|
34879
|
+
for (const dialect of dialects) {
|
|
34880
|
+
for (const backslashEscapes of [false, true]) {
|
|
34881
|
+
collectReferences(tokenize(sql, dialect, backslashEscapes), record);
|
|
34882
|
+
}
|
|
34883
|
+
}
|
|
34884
|
+
return references;
|
|
34885
|
+
}
|
|
34886
|
+
function collectReferences(tokens, record) {
|
|
34887
|
+
const recordName = (parts) => {
|
|
34888
|
+
const bare = parts[parts.length - 1];
|
|
34889
|
+
record(bare);
|
|
34890
|
+
for (const variant of decodedVariants(bare))
|
|
34891
|
+
record(variant);
|
|
34892
|
+
if (parts.length > 1)
|
|
34893
|
+
record(parts.join("."));
|
|
34894
|
+
};
|
|
34895
|
+
let i = 0;
|
|
34896
|
+
while (i < tokens.length) {
|
|
34897
|
+
if (!isKeyword(tokens[i], TABLE_INTRODUCERS)) {
|
|
34898
|
+
i++;
|
|
34899
|
+
continue;
|
|
34900
|
+
}
|
|
34901
|
+
const introducer = tokens[i].value.toUpperCase();
|
|
34902
|
+
const parenMeansFunction = introducer === "FROM" || introducer === "JOIN";
|
|
34903
|
+
let cursor = i + 1;
|
|
34904
|
+
while (isKeyword(tokens[cursor], PRE_TABLE_NOISE))
|
|
34905
|
+
cursor++;
|
|
34906
|
+
while (parenMeansFunction && isPunctuation(tokens[cursor], "(") && !isKeyword(tokens[cursor + 1], SUBQUERY_OPENERS) && tokens[cursor + 1]?.kind === "identifier") {
|
|
34907
|
+
cursor++;
|
|
34908
|
+
}
|
|
34909
|
+
let expectTable = true;
|
|
34910
|
+
while (expectTable) {
|
|
34911
|
+
expectTable = false;
|
|
34912
|
+
const name2 = readQualifiedName(tokens, cursor);
|
|
34913
|
+
if (!name2)
|
|
34914
|
+
break;
|
|
34915
|
+
const isFunctionCall = parenMeansFunction && isPunctuation(tokens[name2.next], "(");
|
|
34916
|
+
if (!isFunctionCall)
|
|
34917
|
+
recordName(name2.parts);
|
|
34918
|
+
cursor = name2.next;
|
|
34919
|
+
if (isFunctionCall)
|
|
34920
|
+
break;
|
|
34921
|
+
if (isKeyword(tokens[cursor], AS_KEYWORD))
|
|
34922
|
+
cursor++;
|
|
34923
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS)) {
|
|
34924
|
+
cursor++;
|
|
34925
|
+
}
|
|
34926
|
+
while (isPunctuation(tokens[cursor], "(")) {
|
|
34927
|
+
let depth = 0;
|
|
34928
|
+
do {
|
|
34929
|
+
if (isPunctuation(tokens[cursor], "("))
|
|
34930
|
+
depth++;
|
|
34931
|
+
else if (isPunctuation(tokens[cursor], ")"))
|
|
34932
|
+
depth--;
|
|
34933
|
+
cursor++;
|
|
34934
|
+
} while (depth > 0 && cursor < tokens.length);
|
|
34935
|
+
if (tokens[cursor]?.kind === "identifier" && !isKeyword(tokens[cursor], POST_TABLE_KEYWORDS))
|
|
34936
|
+
cursor++;
|
|
34937
|
+
}
|
|
34938
|
+
if (isPunctuation(tokens[cursor], ",")) {
|
|
34939
|
+
cursor++;
|
|
34940
|
+
expectTable = true;
|
|
34941
|
+
}
|
|
34942
|
+
}
|
|
34943
|
+
i = Math.max(cursor, i + 1);
|
|
34944
|
+
}
|
|
34945
|
+
let index = 0;
|
|
34946
|
+
while (index < tokens.length) {
|
|
34947
|
+
const token = tokens[index];
|
|
34948
|
+
if (!token || token.kind !== "identifier") {
|
|
34949
|
+
index++;
|
|
34950
|
+
continue;
|
|
34951
|
+
}
|
|
34952
|
+
const name2 = readQualifiedName(tokens, index);
|
|
34953
|
+
for (let part = 0;part < name2.parts.length; part++) {
|
|
34954
|
+
const value = name2.parts[part];
|
|
34955
|
+
const isQuoted = tokens[index + part * 2]?.quoted === true;
|
|
34956
|
+
if (isQuoted || !RESERVED_KEYWORDS.has(value.toUpperCase())) {
|
|
34957
|
+
record(value);
|
|
34958
|
+
for (const variant of decodedVariants(value))
|
|
34959
|
+
record(variant);
|
|
34960
|
+
}
|
|
34961
|
+
}
|
|
34962
|
+
if (name2.parts.length > 1)
|
|
34963
|
+
record(name2.parts.join("."));
|
|
34964
|
+
index = name2.next;
|
|
34965
|
+
}
|
|
34966
|
+
}
|
|
34967
|
+
var TABLE_INTRODUCERS, PRE_TABLE_NOISE, SUBQUERY_OPENERS, AS_KEYWORD, POST_TABLE_KEYWORDS, RESERVED_KEYWORDS, IDENTIFIER_START2, IDENTIFIER_PART, ANY_ESCAPE_SEQUENCE;
|
|
34968
|
+
var init_sql_tables = __esm(() => {
|
|
34969
|
+
init_sql_lexical();
|
|
34970
|
+
TABLE_INTRODUCERS = new Set([
|
|
34971
|
+
"FROM",
|
|
34972
|
+
"JOIN",
|
|
34973
|
+
"INTO",
|
|
34974
|
+
"UPDATE",
|
|
34975
|
+
"TABLE",
|
|
34976
|
+
"TRUNCATE",
|
|
34977
|
+
"COPY",
|
|
34978
|
+
"USING",
|
|
34979
|
+
"STRAIGHT_JOIN"
|
|
34980
|
+
]);
|
|
34981
|
+
PRE_TABLE_NOISE = new Set(["ONLY", "LATERAL", "TABLE"]);
|
|
34982
|
+
SUBQUERY_OPENERS = new Set(["SELECT", "WITH", "VALUES", "TABLE"]);
|
|
34983
|
+
AS_KEYWORD = new Set(["AS"]);
|
|
34984
|
+
POST_TABLE_KEYWORDS = new Set([
|
|
34985
|
+
"AS",
|
|
34986
|
+
"ON",
|
|
34987
|
+
"USING",
|
|
34988
|
+
"WHERE",
|
|
34989
|
+
"GROUP",
|
|
34990
|
+
"ORDER",
|
|
34991
|
+
"HAVING",
|
|
34992
|
+
"LIMIT",
|
|
34993
|
+
"OFFSET",
|
|
34994
|
+
"FETCH",
|
|
34995
|
+
"WINDOW",
|
|
34996
|
+
"UNION",
|
|
34997
|
+
"INTERSECT",
|
|
34998
|
+
"EXCEPT",
|
|
34999
|
+
"JOIN",
|
|
35000
|
+
"INNER",
|
|
35001
|
+
"LEFT",
|
|
35002
|
+
"RIGHT",
|
|
35003
|
+
"FULL",
|
|
35004
|
+
"OUTER",
|
|
35005
|
+
"CROSS",
|
|
35006
|
+
"NATURAL",
|
|
35007
|
+
"STRAIGHT_JOIN",
|
|
35008
|
+
"SET",
|
|
35009
|
+
"VALUES",
|
|
35010
|
+
"SELECT",
|
|
35011
|
+
"RETURNING",
|
|
35012
|
+
"FOR",
|
|
35013
|
+
"INTO",
|
|
35014
|
+
"PARTITION",
|
|
35015
|
+
"WITH",
|
|
35016
|
+
"TABLESAMPLE",
|
|
35017
|
+
"FORCE",
|
|
35018
|
+
"IGNORE",
|
|
35019
|
+
"USE"
|
|
35020
|
+
]);
|
|
35021
|
+
RESERVED_KEYWORDS = new Set([
|
|
35022
|
+
"ALL",
|
|
35023
|
+
"AND",
|
|
35024
|
+
"AS",
|
|
35025
|
+
"ASC",
|
|
35026
|
+
"CASE",
|
|
35027
|
+
"CROSS",
|
|
35028
|
+
"DESC",
|
|
35029
|
+
"DISTINCT",
|
|
35030
|
+
"ELSE",
|
|
35031
|
+
"FALSE",
|
|
35032
|
+
"FOR",
|
|
35033
|
+
"FROM",
|
|
35034
|
+
"GROUP",
|
|
35035
|
+
"HAVING",
|
|
35036
|
+
"IN",
|
|
35037
|
+
"INNER",
|
|
35038
|
+
"INTO",
|
|
35039
|
+
"IS",
|
|
35040
|
+
"JOIN",
|
|
35041
|
+
"LEFT",
|
|
35042
|
+
"LIKE",
|
|
35043
|
+
"LIMIT",
|
|
35044
|
+
"NOT",
|
|
35045
|
+
"NULL",
|
|
35046
|
+
"ON",
|
|
35047
|
+
"OR",
|
|
35048
|
+
"ORDER",
|
|
35049
|
+
"OUTER",
|
|
35050
|
+
"RIGHT",
|
|
35051
|
+
"SELECT",
|
|
35052
|
+
"THEN",
|
|
35053
|
+
"TRUE",
|
|
35054
|
+
"UNION",
|
|
35055
|
+
"USING",
|
|
35056
|
+
"VALUES",
|
|
35057
|
+
"WHEN",
|
|
35058
|
+
"WHERE",
|
|
35059
|
+
"WITH"
|
|
35060
|
+
]);
|
|
35061
|
+
IDENTIFIER_START2 = /[A-Za-z_\u0080-\uFFFF]/;
|
|
35062
|
+
IDENTIFIER_PART = /[A-Za-z0-9_$\u0080-\uFFFF]/;
|
|
35063
|
+
ANY_ESCAPE_SEQUENCE = /([^0-9a-fA-F+'"\s])(?:\+([0-9a-fA-F]{6})|([0-9a-fA-F]{4}))/g;
|
|
35064
|
+
});
|
|
35065
|
+
|
|
34436
35066
|
// src/core/limits.ts
|
|
34437
35067
|
var DEFAULT_QUERY_ONLY_LIMIT = 1000;
|
|
34438
35068
|
|
|
@@ -34486,11 +35116,9 @@ class QueryExecutor {
|
|
|
34486
35116
|
}
|
|
34487
35117
|
}
|
|
34488
35118
|
}
|
|
34489
|
-
|
|
34490
|
-
|
|
34491
|
-
|
|
34492
|
-
this.blacklistValidator.checkTableBlacklist(classification.type, tableName, []);
|
|
34493
|
-
}
|
|
35119
|
+
const referencedTables = this.blacklistValidator ? extractTableReferences(sql, { dialect: this.resolveDialect() }) : [];
|
|
35120
|
+
if (this.blacklistValidator && referencedTables.length > 0) {
|
|
35121
|
+
this.blacklistValidator.checkTablesBlacklist(classification.type, referencedTables);
|
|
34494
35122
|
}
|
|
34495
35123
|
const resultData = await this.adapter.execute(executeSql);
|
|
34496
35124
|
const executionTimeMs = Math.round(performance.now() - start);
|
|
@@ -34503,15 +35131,12 @@ class QueryExecutor {
|
|
|
34503
35131
|
let securityNotification;
|
|
34504
35132
|
let omittedColumns = [];
|
|
34505
35133
|
if (this.blacklistValidator) {
|
|
34506
|
-
const
|
|
34507
|
-
|
|
34508
|
-
|
|
34509
|
-
|
|
34510
|
-
|
|
34511
|
-
|
|
34512
|
-
columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
|
|
34513
|
-
securityNotification = this.blacklistValidator.buildSecurityNotification(tableName, filterResult.omittedColumns);
|
|
34514
|
-
}
|
|
35134
|
+
const filterResult = this.blacklistValidator.filterColumnsForTables(referencedTables, rows, columnNames);
|
|
35135
|
+
filteredRows = filterResult.filteredRows;
|
|
35136
|
+
if (filterResult.omittedColumns.length > 0) {
|
|
35137
|
+
omittedColumns = filterResult.omittedColumns;
|
|
35138
|
+
columnNames = columnNames.filter((col) => !filterResult.omittedColumns.includes(col));
|
|
35139
|
+
securityNotification = this.blacklistValidator.buildSecurityNotification(referencedTables[0] ?? "", filterResult.omittedColumns);
|
|
34515
35140
|
}
|
|
34516
35141
|
}
|
|
34517
35142
|
if (options?.fieldSelection) {
|
|
@@ -34617,13 +35242,30 @@ function inferColumnType(value) {
|
|
|
34617
35242
|
var init_query_executor = __esm(() => {
|
|
34618
35243
|
init_permission_guard();
|
|
34619
35244
|
init_error_suggester();
|
|
34620
|
-
|
|
35245
|
+
init_sql_tables();
|
|
34621
35246
|
init_applied_limit();
|
|
34622
35247
|
init_integration_helper();
|
|
34623
35248
|
init_field_projection();
|
|
34624
35249
|
});
|
|
34625
35250
|
|
|
34626
35251
|
// src/core/mongo/field-masker.ts
|
|
35252
|
+
function maskMongoRowsForCollections(rows, collections, blacklist) {
|
|
35253
|
+
const columns = blacklist.columns ?? {};
|
|
35254
|
+
return collections.reduce((masked, entry) => {
|
|
35255
|
+
const scope = typeof entry === "string" ? { collection: entry } : entry;
|
|
35256
|
+
const atTopLevel = maskMongoRows(masked, scope.collection, blacklist);
|
|
35257
|
+
if (!scope.prefix)
|
|
35258
|
+
return atTopLevel;
|
|
35259
|
+
const rules = columns[scope.collection] ?? findCaseInsensitive(columns, scope.collection);
|
|
35260
|
+
if (!rules || rules.length === 0)
|
|
35261
|
+
return atTopLevel;
|
|
35262
|
+
const prefixKey = `\x00${scope.collection}@${scope.prefix}`;
|
|
35263
|
+
return maskMongoRows(atTopLevel, prefixKey, {
|
|
35264
|
+
...blacklist,
|
|
35265
|
+
columns: { ...columns, [prefixKey]: rules.map((rule) => `${scope.prefix}.${rule}`) }
|
|
35266
|
+
});
|
|
35267
|
+
}, rows);
|
|
35268
|
+
}
|
|
34627
35269
|
function maskMongoRows(rows, collection, blacklist) {
|
|
34628
35270
|
const columns = blacklist.columns ?? {};
|
|
34629
35271
|
const raw = columns[collection] ?? findCaseInsensitive(columns, collection);
|
|
@@ -34676,6 +35318,86 @@ function findCaseInsensitive(columns, name2) {
|
|
|
34676
35318
|
var REDACTED2 = "[REDACTED]";
|
|
34677
35319
|
var init_field_masker = () => {};
|
|
34678
35320
|
|
|
35321
|
+
// src/core/mongo/collection-references.ts
|
|
35322
|
+
function recordTarget(value, found) {
|
|
35323
|
+
if (typeof value === "string") {
|
|
35324
|
+
if (value.length > 0)
|
|
35325
|
+
found.add(value);
|
|
35326
|
+
return;
|
|
35327
|
+
}
|
|
35328
|
+
if (value === null || typeof value !== "object")
|
|
35329
|
+
return;
|
|
35330
|
+
const record = value;
|
|
35331
|
+
for (const field of COLLECTION_FIELDS) {
|
|
35332
|
+
const target = record[field];
|
|
35333
|
+
if (typeof target === "string" && target.length > 0)
|
|
35334
|
+
found.add(target);
|
|
35335
|
+
else if (target !== null && typeof target === "object")
|
|
35336
|
+
recordTarget(target, found);
|
|
35337
|
+
}
|
|
35338
|
+
}
|
|
35339
|
+
function joinPrefix(parent, child) {
|
|
35340
|
+
if (!parent)
|
|
35341
|
+
return child;
|
|
35342
|
+
if (!child)
|
|
35343
|
+
return parent;
|
|
35344
|
+
return `${parent}.${child}`;
|
|
35345
|
+
}
|
|
35346
|
+
function collect2(pipeline, found, scopes, parentPrefix) {
|
|
35347
|
+
if (!Array.isArray(pipeline))
|
|
35348
|
+
return;
|
|
35349
|
+
for (const stage of pipeline) {
|
|
35350
|
+
if (stage === null || typeof stage !== "object")
|
|
35351
|
+
continue;
|
|
35352
|
+
const record = stage;
|
|
35353
|
+
for (const name2 of COLLECTION_STAGES) {
|
|
35354
|
+
if (!Object.prototype.hasOwnProperty.call(record, name2))
|
|
35355
|
+
continue;
|
|
35356
|
+
const stageTargets = new Set;
|
|
35357
|
+
recordTarget(record[name2], stageTargets);
|
|
35358
|
+
for (const collection of stageTargets)
|
|
35359
|
+
found.add(collection);
|
|
35360
|
+
const value = record[name2];
|
|
35361
|
+
const as = value !== null && typeof value === "object" ? value["as"] : undefined;
|
|
35362
|
+
const prefix = joinPrefix(parentPrefix, typeof as === "string" && as ? as : undefined);
|
|
35363
|
+
for (const collection of stageTargets) {
|
|
35364
|
+
scopes.push({ collection, ...prefix ? { prefix } : {} });
|
|
35365
|
+
}
|
|
35366
|
+
}
|
|
35367
|
+
for (const holder of SUB_PIPELINE_HOLDERS2) {
|
|
35368
|
+
const value = record[holder];
|
|
35369
|
+
if (value === undefined || value === null)
|
|
35370
|
+
continue;
|
|
35371
|
+
if (Array.isArray(value)) {
|
|
35372
|
+
collect2(value, found, scopes, parentPrefix);
|
|
35373
|
+
continue;
|
|
35374
|
+
}
|
|
35375
|
+
if (typeof value !== "object")
|
|
35376
|
+
continue;
|
|
35377
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
35378
|
+
const holderPrefix = holder === "$facet" ? joinPrefix(parentPrefix, key) : joinPrefix(parentPrefix, typeof value["as"] === "string" ? value["as"] : undefined);
|
|
35379
|
+
collect2(nested, found, scopes, holderPrefix);
|
|
35380
|
+
}
|
|
35381
|
+
}
|
|
35382
|
+
}
|
|
35383
|
+
}
|
|
35384
|
+
function findMongoCollectionReferences(pipeline) {
|
|
35385
|
+
const found = new Set;
|
|
35386
|
+
collect2(pipeline, found, []);
|
|
35387
|
+
return Array.from(found);
|
|
35388
|
+
}
|
|
35389
|
+
function findMongoCollectionScopes(pipeline) {
|
|
35390
|
+
const scopes = [];
|
|
35391
|
+
collect2(pipeline, new Set, scopes);
|
|
35392
|
+
return scopes;
|
|
35393
|
+
}
|
|
35394
|
+
var COLLECTION_FIELDS, SUB_PIPELINE_HOLDERS2, COLLECTION_STAGES;
|
|
35395
|
+
var init_collection_references = __esm(() => {
|
|
35396
|
+
COLLECTION_FIELDS = ["from", "coll", "into"];
|
|
35397
|
+
SUB_PIPELINE_HOLDERS2 = ["$facet", "$lookup", "$unionWith", "$graphLookup"];
|
|
35398
|
+
COLLECTION_STAGES = ["$lookup", "$unionWith", "$graphLookup", "$out", "$merge"];
|
|
35399
|
+
});
|
|
35400
|
+
|
|
34679
35401
|
// src/core/query-input.ts
|
|
34680
35402
|
function trimOuterWhitespace(value) {
|
|
34681
35403
|
let start = 0;
|
|
@@ -34857,6 +35579,13 @@ var init_query_size_guard = __esm(() => {
|
|
|
34857
35579
|
import crypto3 from "crypto";
|
|
34858
35580
|
import { tmpdir } from "os";
|
|
34859
35581
|
import { join as join23 } from "path";
|
|
35582
|
+
function mongoCollectionRefs(query) {
|
|
35583
|
+
try {
|
|
35584
|
+
return findMongoCollectionScopes(JSON.parse(query));
|
|
35585
|
+
} catch {
|
|
35586
|
+
return [];
|
|
35587
|
+
}
|
|
35588
|
+
}
|
|
34860
35589
|
function requireSqlConnection2(connection) {
|
|
34861
35590
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
34862
35591
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
@@ -35008,17 +35737,22 @@ async function preflightQuery(query, options, context, fieldSelection, multiConn
|
|
|
35008
35737
|
await preflightSqlSizeGuard(query, options, config);
|
|
35009
35738
|
}
|
|
35010
35739
|
async function preflightSqlSizeGuard(query, options, config) {
|
|
35011
|
-
|
|
35012
|
-
const mainTable = extractTableName2(query);
|
|
35013
|
-
if (!mainTable || !config.schema || options.noLimit)
|
|
35014
|
-
return;
|
|
35015
|
-
const tableSchema = config.schema[mainTable];
|
|
35016
|
-
if (!tableSchema)
|
|
35740
|
+
if (!config.schema || options.noLimit)
|
|
35017
35741
|
return;
|
|
35742
|
+
const { extractTableReferences: extractTableReferences2 } = await Promise.resolve().then(() => (init_sql_tables(), exports_sql_tables));
|
|
35743
|
+
const { SQL_DIALECTS: SQL_DIALECTS2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
|
|
35744
|
+
const dialect = SQL_DIALECTS2.find((candidate) => candidate === config.connection?.system);
|
|
35745
|
+
const tables = extractTableReferences2(query, { ...dialect ? { dialect } : {} });
|
|
35746
|
+
const schema = config.schema;
|
|
35018
35747
|
const { shouldBlockQuery: shouldBlockQuery2 } = await Promise.resolve().then(() => (init_query_size_guard(), exports_query_size_guard));
|
|
35019
|
-
const
|
|
35020
|
-
|
|
35021
|
-
|
|
35748
|
+
for (const table of tables) {
|
|
35749
|
+
const tableSchema = schema[table];
|
|
35750
|
+
if (!tableSchema)
|
|
35751
|
+
continue;
|
|
35752
|
+
const guard = shouldBlockQuery2(query, tableSchema);
|
|
35753
|
+
if (guard.blocked)
|
|
35754
|
+
throw new Error(`\u26A0 ${guard.reason}`);
|
|
35755
|
+
}
|
|
35022
35756
|
}
|
|
35023
35757
|
async function preflightMongoQuery(query, options, config, multiConnection) {
|
|
35024
35758
|
const collection = options.collection;
|
|
@@ -35044,7 +35778,10 @@ async function preflightMongoQuery(query, options, config, multiConnection) {
|
|
|
35044
35778
|
context: "MongoDB multi-connection pipelines"
|
|
35045
35779
|
});
|
|
35046
35780
|
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
35047
|
-
blacklistValidator.
|
|
35781
|
+
blacklistValidator.checkTablesBlacklist("SELECT", [
|
|
35782
|
+
collection,
|
|
35783
|
+
...findMongoCollectionReferences(parsedQuery)
|
|
35784
|
+
]);
|
|
35048
35785
|
if (!config.schema || options.noLimit)
|
|
35049
35786
|
return;
|
|
35050
35787
|
const tableSchema = config.schema[collection];
|
|
@@ -35068,7 +35805,7 @@ function preflightElasticsearchQuery(query, options, config, multiConnection) {
|
|
|
35068
35805
|
JSON.parse(body);
|
|
35069
35806
|
enforceElasticsearchPermission({ method: "POST", apiPath: `/${indexName}/_search`, body }, multiConnection ? "query-only" : config.permission);
|
|
35070
35807
|
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
35071
|
-
blacklistValidator.
|
|
35808
|
+
blacklistValidator.checkIndexBlacklist("SELECT", indexName);
|
|
35072
35809
|
}
|
|
35073
35810
|
async function executeConnectionQuery(query, options, context, fieldSelection) {
|
|
35074
35811
|
const { config, configPath, connectionName } = context;
|
|
@@ -35221,7 +35958,7 @@ async function mongoQueryBranch(queryStr, options, context, fieldSelection) {
|
|
|
35221
35958
|
const limitedResult = appliedLimit === undefined ? undefined : trimAppliedLimit(result.rows, appliedLimit);
|
|
35222
35959
|
const visibleRows = limitedResult?.rows ?? result.rows;
|
|
35223
35960
|
const blacklistCfg = config.blacklist ?? { tables: [], columns: {} };
|
|
35224
|
-
const maskedRows =
|
|
35961
|
+
const maskedRows = maskMongoRowsForCollections(visibleRows, [collection, ...mongoCollectionRefs(queryStr)], blacklistCfg);
|
|
35225
35962
|
const projected = fieldSelection ? projectRows(maskedRows, fieldSelection) : undefined;
|
|
35226
35963
|
const outputRows = projected?.rows ?? maskedRows;
|
|
35227
35964
|
const columnNames = projected?.columnNames ?? (outputRows[0] ? Object.keys(outputRows[0]) : []);
|
|
@@ -35325,7 +36062,7 @@ async function elasticsearchQueryBranch(queryStr, options, context) {
|
|
|
35325
36062
|
const limitedResult = appliedLimit === undefined ? undefined : trimAppliedLimit(result.rows, appliedLimit);
|
|
35326
36063
|
const visibleRows = limitedResult?.rows ?? result.rows;
|
|
35327
36064
|
const columnNames = visibleRows[0] ? Object.keys(visibleRows[0]) : [];
|
|
35328
|
-
const filterResult = blacklistValidator.
|
|
36065
|
+
const filterResult = blacklistValidator.filterColumnsForIndexExpression(indexName, visibleRows, columnNames);
|
|
35329
36066
|
const queryResult = {
|
|
35330
36067
|
rows: filterResult.filteredRows,
|
|
35331
36068
|
rowCount: filterResult.filteredRows.length,
|
|
@@ -35387,6 +36124,7 @@ var init_query = __esm(() => {
|
|
|
35387
36124
|
init_applied_limit();
|
|
35388
36125
|
init_integration_helper();
|
|
35389
36126
|
init_field_masker();
|
|
36127
|
+
init_collection_references();
|
|
35390
36128
|
init_types4();
|
|
35391
36129
|
init_query_input();
|
|
35392
36130
|
init_field_projection();
|
|
@@ -35950,7 +36688,8 @@ async function qMongoBranch(snippet, prepared, options, config) {
|
|
|
35950
36688
|
});
|
|
35951
36689
|
const blacklistManager = new BlacklistManager(config);
|
|
35952
36690
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
35953
|
-
|
|
36691
|
+
const collections = [collection, ...findMongoCollectionReferences(parsedBody)];
|
|
36692
|
+
blacklistValidator.checkTablesBlacklist("SELECT", collections);
|
|
35954
36693
|
if (options.dryRun) {
|
|
35955
36694
|
console.log(`Dry-run preview (no execution):`);
|
|
35956
36695
|
console.log(`Collection: ${collection}`);
|
|
@@ -35965,7 +36704,7 @@ async function qMongoBranch(snippet, prepared, options, config) {
|
|
|
35965
36704
|
const result = await adapter.execute(prepared.driver.sql, [collection]);
|
|
35966
36705
|
const executionTimeMs = Math.round(performance.now() - start);
|
|
35967
36706
|
const blacklistCfg = config.blacklist ?? { tables: [], columns: {} };
|
|
35968
|
-
const masked =
|
|
36707
|
+
const masked = maskMongoRowsForCollections(result.rows, [collection, ...findMongoCollectionScopes(parsedBody)], blacklistCfg);
|
|
35969
36708
|
const securityNotification = (blacklistCfg.columns[collection] ?? []).length > 0 ? "Some fields may have been redacted as [REDACTED] per .dbcli blacklist." : undefined;
|
|
35970
36709
|
if (options.ui || options.format === "html") {
|
|
35971
36710
|
const html = await generateHtmlReport({
|
|
@@ -36006,6 +36745,7 @@ var init_q_mongo = __esm(() => {
|
|
|
36006
36745
|
init_adapters();
|
|
36007
36746
|
init_blacklist_validator();
|
|
36008
36747
|
init_field_masker();
|
|
36748
|
+
init_collection_references();
|
|
36009
36749
|
init_formatters();
|
|
36010
36750
|
init_html_formatter();
|
|
36011
36751
|
init_opener();
|
|
@@ -36053,7 +36793,8 @@ async function qCommand(name2, options, command) {
|
|
|
36053
36793
|
config = await configModule.read(configPath);
|
|
36054
36794
|
if (!config.connection)
|
|
36055
36795
|
throw new Error('Run "dbcli init" first');
|
|
36056
|
-
const
|
|
36796
|
+
const connectionSystem = config.connection.system;
|
|
36797
|
+
const engine = mapSystemToEngine(connectionSystem);
|
|
36057
36798
|
const dirs = resolveSnippetDirs(process.cwd());
|
|
36058
36799
|
const map = await loadSnippets(dirs);
|
|
36059
36800
|
const snippet = resolveByName(map, name2, engine);
|
|
@@ -36088,10 +36829,16 @@ async function qCommand(name2, options, command) {
|
|
|
36088
36829
|
const blacklistManager = new BlacklistManager(config);
|
|
36089
36830
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
36090
36831
|
const family = engineFamily(engine);
|
|
36091
|
-
const
|
|
36832
|
+
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
36833
|
+
const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
36834
|
+
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
36835
|
+
}) : family === "es" ? [prepared.execHints?.index ?? ""] : [];
|
|
36836
|
+
const targetName = targets[0] ?? "";
|
|
36092
36837
|
targetNameForAudit = targetName || name2;
|
|
36093
|
-
if (family
|
|
36094
|
-
blacklistValidator.
|
|
36838
|
+
if (family === "es") {
|
|
36839
|
+
blacklistValidator.checkIndexBlacklist("SELECT", targetName);
|
|
36840
|
+
} else if (family !== "redis" && targets.length > 0) {
|
|
36841
|
+
blacklistValidator.checkTablesBlacklist("SELECT", targets);
|
|
36095
36842
|
}
|
|
36096
36843
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
36097
36844
|
await adapter.connect();
|
|
@@ -36106,7 +36853,7 @@ async function qCommand(name2, options, command) {
|
|
|
36106
36853
|
const limitedResult = prepared.guardLimit === undefined ? undefined : trimAppliedLimit(result.rows, prepared.guardLimit);
|
|
36107
36854
|
const resultRows = limitedResult?.rows ?? result.rows;
|
|
36108
36855
|
const columnNames = resultRows[0] ? Object.keys(resultRows[0]) : [];
|
|
36109
|
-
const filtered = family === "redis" ? { filteredRows: resultRows, omittedColumns: [] } : blacklistValidator.
|
|
36856
|
+
const filtered = family === "redis" ? { filteredRows: resultRows, omittedColumns: [] } : family === "es" ? blacklistValidator.filterColumnsForIndexExpression(targetName, resultRows, columnNames) : blacklistValidator.filterColumnsForTables(targets, resultRows, columnNames);
|
|
36110
36857
|
const securityNotification = family === "redis" || filtered.omittedColumns.length === 0 ? undefined : blacklistValidator.buildSecurityNotification(targetName, filtered.omittedColumns);
|
|
36111
36858
|
if (options.ui || options.format === "html") {
|
|
36112
36859
|
const html = await generateHtmlReport({
|
|
@@ -36166,6 +36913,11 @@ async function qCommand(name2, options, command) {
|
|
|
36166
36913
|
} else {
|
|
36167
36914
|
try {
|
|
36168
36915
|
console.error(colors.dim(`Executing verification query: ${verifySpec.query}`));
|
|
36916
|
+
if (family === "sql") {
|
|
36917
|
+
blacklistValidator.checkTablesBlacklist("SELECT", extractTableReferences(verifySpec.query, {
|
|
36918
|
+
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
36919
|
+
}));
|
|
36920
|
+
}
|
|
36169
36921
|
const verifyResult = await adapter.execute(verifySpec.query);
|
|
36170
36922
|
const firstRow = verifyResult.rows[0];
|
|
36171
36923
|
const evalResult = evaluateExpectation(firstRow, verifySpec.expects);
|
|
@@ -36348,7 +37100,7 @@ var init_q = __esm(() => {
|
|
|
36348
37100
|
init_blacklist_validator();
|
|
36349
37101
|
init_blacklist();
|
|
36350
37102
|
init_permission_guard();
|
|
36351
|
-
|
|
37103
|
+
init_sql_tables();
|
|
36352
37104
|
init_formatters();
|
|
36353
37105
|
init_html_formatter();
|
|
36354
37106
|
init_opener();
|
|
@@ -39186,7 +39938,8 @@ async function exportCommand(sql, options, command) {
|
|
|
39186
39938
|
}
|
|
39187
39939
|
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection));
|
|
39188
39940
|
await adapter.connect();
|
|
39189
|
-
const
|
|
39941
|
+
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
39942
|
+
const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, {
|
|
39190
39943
|
recovery: options.recovery,
|
|
39191
39944
|
deferDiagnostics: true
|
|
39192
39945
|
});
|
|
@@ -39329,15 +40082,19 @@ async function esExportBranch(query, options, config) {
|
|
|
39329
40082
|
let rowCount;
|
|
39330
40083
|
const diagnostics = [];
|
|
39331
40084
|
try {
|
|
40085
|
+
const declaredTarget = query.trim().startsWith("{") ? options.index ?? options.collection : query.trim();
|
|
40086
|
+
if (declaredTarget)
|
|
40087
|
+
blacklistValidator.checkIndexBlacklist("SELECT", declaredTarget);
|
|
39332
40088
|
const {
|
|
39333
40089
|
rows: fetched,
|
|
39334
40090
|
target,
|
|
39335
40091
|
cap: cap2
|
|
39336
40092
|
} = await buildEsExportRows(query, options, adapter);
|
|
39337
|
-
blacklistValidator.
|
|
40093
|
+
blacklistValidator.checkIndexBlacklist("SELECT", target);
|
|
39338
40094
|
const limitedResult = cap2 === undefined ? undefined : trimAppliedLimit(fetched, cap2);
|
|
39339
40095
|
assertExportNotSilentlyTruncated(limitedResult?.metadata, options);
|
|
39340
|
-
const
|
|
40096
|
+
const visibleRows = limitedResult?.rows ?? fetched;
|
|
40097
|
+
const rows = blacklistValidator.filterColumnsForIndexExpression(target, visibleRows, collectColumnUnion(visibleRows)).filteredRows;
|
|
39341
40098
|
rowCount = rows.length;
|
|
39342
40099
|
const columns = collectColumnUnion(rows);
|
|
39343
40100
|
if (options.format === "html") {
|
|
@@ -39393,7 +40150,9 @@ async function mongoExportBranch(query, options, config) {
|
|
|
39393
40150
|
});
|
|
39394
40151
|
const blacklistManager = new BlacklistManager(config);
|
|
39395
40152
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
39396
|
-
|
|
40153
|
+
const mongoScopes = findMongoCollectionScopes(JSON.parse(query));
|
|
40154
|
+
const mongoCollections = [collection, ...findMongoCollectionReferences(JSON.parse(query))];
|
|
40155
|
+
blacklistValidator.checkTablesBlacklist("SELECT", mongoCollections);
|
|
39397
40156
|
let effectiveLimit;
|
|
39398
40157
|
if (options.noLimit) {
|
|
39399
40158
|
effectiveLimit = undefined;
|
|
@@ -39413,7 +40172,7 @@ async function mongoExportBranch(query, options, config) {
|
|
|
39413
40172
|
const limitedResult = effectiveLimit === undefined ? undefined : trimAppliedLimit(result.rows, effectiveLimit);
|
|
39414
40173
|
assertExportNotSilentlyTruncated(limitedResult?.metadata, options);
|
|
39415
40174
|
const blacklistCfg = config.blacklist ?? { tables: [], columns: {} };
|
|
39416
|
-
const maskedRows =
|
|
40175
|
+
const maskedRows = maskMongoRowsForCollections(limitedResult?.rows ?? result.rows, [collection, ...mongoScopes], blacklistCfg);
|
|
39417
40176
|
rowCount = maskedRows.length;
|
|
39418
40177
|
hasBlacklistedColumns = (blacklistCfg.columns[collection] ?? []).length > 0;
|
|
39419
40178
|
const visibleColumns = collectColumnUnion(maskedRows);
|
|
@@ -39542,6 +40301,7 @@ var init_export = __esm(() => {
|
|
|
39542
40301
|
init_integration_helper();
|
|
39543
40302
|
init_engine_hints();
|
|
39544
40303
|
init_field_masker();
|
|
40304
|
+
init_collection_references();
|
|
39545
40305
|
SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
|
|
39546
40306
|
});
|
|
39547
40307
|
|
|
@@ -93845,6 +94605,25 @@ async function runDiagnostic(input) {
|
|
|
93845
94605
|
return { ...base, rowCount: 0, rows: [], status: "skipped", reason, durationMs: 0 };
|
|
93846
94606
|
}
|
|
93847
94607
|
const family = engineFamily(input.engine);
|
|
94608
|
+
const referencedTables = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
94609
|
+
...ENGINE_DIALECT2[input.engine] ? { dialect: ENGINE_DIALECT2[input.engine] } : {}
|
|
94610
|
+
}) : family === "es" && prepared.execHints?.index ? [prepared.execHints.index] : [];
|
|
94611
|
+
if (input.blacklistValidator && referencedTables.length > 0) {
|
|
94612
|
+
try {
|
|
94613
|
+
input.blacklistValidator.checkTablesBlacklist("SELECT", referencedTables);
|
|
94614
|
+
} catch (err) {
|
|
94615
|
+
if (!(err instanceof BlacklistError))
|
|
94616
|
+
throw err;
|
|
94617
|
+
return {
|
|
94618
|
+
...base,
|
|
94619
|
+
rowCount: 0,
|
|
94620
|
+
rows: [],
|
|
94621
|
+
status: "skipped",
|
|
94622
|
+
reason: err.message,
|
|
94623
|
+
durationMs: 0
|
|
94624
|
+
};
|
|
94625
|
+
}
|
|
94626
|
+
}
|
|
93848
94627
|
const start = performance.now();
|
|
93849
94628
|
const exec2 = (async () => {
|
|
93850
94629
|
const indexParams = family === "es" && prepared.execHints?.index ? [prepared.execHints.index] : [];
|
|
@@ -93880,7 +94659,8 @@ async function runDiagnostic(input) {
|
|
|
93880
94659
|
durationMs
|
|
93881
94660
|
};
|
|
93882
94661
|
}
|
|
93883
|
-
const
|
|
94662
|
+
const fetched = outcome.rows ?? [];
|
|
94663
|
+
const rows = input.blacklistValidator ? input.blacklistValidator.filterColumnsForTables(referencedTables, fetched, fetched[0] ? Object.keys(fetched[0]) : []).filteredRows : fetched;
|
|
93884
94664
|
if (rows.length === 0) {
|
|
93885
94665
|
return { ...base, rowCount: 0, rows: [], status: "no-data", durationMs };
|
|
93886
94666
|
}
|
|
@@ -93892,9 +94672,17 @@ async function runDiagnostic(input) {
|
|
|
93892
94672
|
durationMs
|
|
93893
94673
|
};
|
|
93894
94674
|
}
|
|
94675
|
+
var ENGINE_DIALECT2;
|
|
93895
94676
|
var init_run_diagnostic = __esm(() => {
|
|
93896
94677
|
init_saved_queries();
|
|
93897
94678
|
init_strategies();
|
|
94679
|
+
init_sql_tables();
|
|
94680
|
+
init_blacklist();
|
|
94681
|
+
ENGINE_DIALECT2 = {
|
|
94682
|
+
postgres: "postgresql",
|
|
94683
|
+
mysql: "mysql",
|
|
94684
|
+
mariadb: "mariadb"
|
|
94685
|
+
};
|
|
93898
94686
|
});
|
|
93899
94687
|
|
|
93900
94688
|
// src/core/report/collector.ts
|
|
@@ -93950,6 +94738,7 @@ async function collectReport(opts) {
|
|
|
93950
94738
|
return finalize({ context, sections: [], warnings, generatedAt });
|
|
93951
94739
|
}
|
|
93952
94740
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
94741
|
+
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
93953
94742
|
const sectionEvidence = new Map;
|
|
93954
94743
|
for (const id of sections)
|
|
93955
94744
|
sectionEvidence.set(id, []);
|
|
@@ -93963,7 +94752,8 @@ async function collectReport(opts) {
|
|
|
93963
94752
|
adapter,
|
|
93964
94753
|
engine,
|
|
93965
94754
|
timeoutMs: timeout,
|
|
93966
|
-
maxRows
|
|
94755
|
+
maxRows,
|
|
94756
|
+
blacklistValidator
|
|
93967
94757
|
});
|
|
93968
94758
|
const sectionId = sectionForIntent(ev.intent);
|
|
93969
94759
|
if (sectionId && sectionEvidence.has(sectionId)) {
|
|
@@ -94027,6 +94817,7 @@ var init_collector2 = __esm(() => {
|
|
|
94027
94817
|
init_saved_queries();
|
|
94028
94818
|
init_select_snippets();
|
|
94029
94819
|
init_run_diagnostic();
|
|
94820
|
+
init_blacklist_validator();
|
|
94030
94821
|
init_section_map();
|
|
94031
94822
|
init_types7();
|
|
94032
94823
|
});
|
|
@@ -97934,7 +98725,8 @@ var init_snapshot = __esm(() => {
|
|
|
97934
98725
|
init_validation();
|
|
97935
98726
|
init_blacklist_validator();
|
|
97936
98727
|
init_query_executor();
|
|
97937
|
-
|
|
98728
|
+
init_permission_guard();
|
|
98729
|
+
init_sql_tables();
|
|
97938
98730
|
init_fingerprint();
|
|
97939
98731
|
init_serializer();
|
|
97940
98732
|
init_saved_queries();
|
|
@@ -97962,10 +98754,12 @@ var init_snapshot = __esm(() => {
|
|
|
97962
98754
|
try {
|
|
97963
98755
|
const blacklistManager = new BlacklistManager(config);
|
|
97964
98756
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
98757
|
+
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === config.connection?.system);
|
|
97965
98758
|
const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, options);
|
|
97966
98759
|
const result = await executor3.execute(sql, { autoLimit: options.limit !== false });
|
|
97967
|
-
const
|
|
97968
|
-
|
|
98760
|
+
const redactedColumns = Array.from(new Set(extractTableReferences(sql, {
|
|
98761
|
+
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
98762
|
+
}).flatMap((table) => blacklistManager.getBlacklistedColumns(table))));
|
|
97969
98763
|
const snap = buildFingerprint(result, {
|
|
97970
98764
|
includeRows: options.rows === true,
|
|
97971
98765
|
redactedColumns,
|
|
@@ -99531,8 +100325,20 @@ function buildRealRunners(ctx) {
|
|
|
99531
100325
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99532
100326
|
const schema = config.schema ?? {};
|
|
99533
100327
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99534
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
99535
|
-
|
|
100328
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100329
|
+
sql: sql.trim(),
|
|
100330
|
+
permission: config.permission,
|
|
100331
|
+
blacklist,
|
|
100332
|
+
schemaLookup,
|
|
100333
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100334
|
+
});
|
|
100335
|
+
const analyzePlan = (sql) => analyzeQueryRisk({
|
|
100336
|
+
sql: sql.trim(),
|
|
100337
|
+
permission: "read-write",
|
|
100338
|
+
blacklist,
|
|
100339
|
+
schemaLookup,
|
|
100340
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100341
|
+
});
|
|
99536
100342
|
return {
|
|
99537
100343
|
blacklistGuard: async (table) => {
|
|
99538
100344
|
const bm = new BlacklistManager(config);
|
|
@@ -99605,7 +100411,13 @@ function buildMigrationRunners(ctx) {
|
|
|
99605
100411
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99606
100412
|
const schema = config.schema ?? {};
|
|
99607
100413
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99608
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
100414
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100415
|
+
sql: sql.trim(),
|
|
100416
|
+
permission: config.permission,
|
|
100417
|
+
blacklist,
|
|
100418
|
+
schemaLookup,
|
|
100419
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100420
|
+
});
|
|
99609
100421
|
return {
|
|
99610
100422
|
blacklistGuard: async (table) => {
|
|
99611
100423
|
const bm = new BlacklistManager(config);
|
|
@@ -99673,8 +100485,20 @@ function buildRollbackRunners(ctx, input) {
|
|
|
99673
100485
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99674
100486
|
const schema = config.schema ?? {};
|
|
99675
100487
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99676
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
99677
|
-
|
|
100488
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100489
|
+
sql: sql.trim(),
|
|
100490
|
+
permission: config.permission,
|
|
100491
|
+
blacklist,
|
|
100492
|
+
schemaLookup,
|
|
100493
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100494
|
+
});
|
|
100495
|
+
const analyzePlan = (sql) => analyzeQueryRisk({
|
|
100496
|
+
sql: sql.trim(),
|
|
100497
|
+
permission: "read-write",
|
|
100498
|
+
blacklist,
|
|
100499
|
+
schemaLookup,
|
|
100500
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100501
|
+
});
|
|
99678
100502
|
const ddlStatementGuard = async (statement, table) => {
|
|
99679
100503
|
if (!isSingleStatement(statement)) {
|
|
99680
100504
|
return {
|
|
@@ -99786,7 +100610,13 @@ function buildConstraintRunners(ctx, input) {
|
|
|
99786
100610
|
const blacklist = config.blacklist ?? { tables: [], columns: {} };
|
|
99787
100611
|
const schema = config.schema ?? {};
|
|
99788
100612
|
const schemaLookup = { tables: schema, cacheAvailable: Object.keys(schema).length > 0 };
|
|
99789
|
-
const analyze = (sql) => analyzeQueryRisk({
|
|
100613
|
+
const analyze = (sql) => analyzeQueryRisk({
|
|
100614
|
+
sql: sql.trim(),
|
|
100615
|
+
permission: config.permission,
|
|
100616
|
+
blacklist,
|
|
100617
|
+
schemaLookup,
|
|
100618
|
+
dialect: toSqlDialect(config.connection?.system)
|
|
100619
|
+
});
|
|
99790
100620
|
const engine = constraintEngineOf(config.connection.system);
|
|
99791
100621
|
const violationSql = buildViolationQuery(input, engine);
|
|
99792
100622
|
const columnsExist = async (table, cols) => {
|
|
@@ -103041,17 +103871,20 @@ class ReplEngine {
|
|
|
103041
103871
|
};
|
|
103042
103872
|
}
|
|
103043
103873
|
}
|
|
103044
|
-
|
|
103045
|
-
|
|
103046
|
-
|
|
103047
|
-
|
|
103048
|
-
|
|
103049
|
-
|
|
103050
|
-
|
|
103051
|
-
|
|
103052
|
-
|
|
103053
|
-
|
|
103054
|
-
|
|
103874
|
+
const referencedTables = extractTableReferences(sql, {
|
|
103875
|
+
dialect: SQL_DIALECTS.find((dialect) => dialect === this.context.system)
|
|
103876
|
+
});
|
|
103877
|
+
const blacklistValidator = this.config?.blacklist ? new BlacklistValidator(new BlacklistManager(this.config)) : undefined;
|
|
103878
|
+
if (blacklistValidator && referencedTables.length > 0) {
|
|
103879
|
+
try {
|
|
103880
|
+
blacklistValidator.checkTablesBlacklist("SELECT", referencedTables);
|
|
103881
|
+
} catch (error) {
|
|
103882
|
+
if (!(error instanceof BlacklistError))
|
|
103883
|
+
throw error;
|
|
103884
|
+
return {
|
|
103885
|
+
action: "continue",
|
|
103886
|
+
output: import_picocolors3.default.red(t_vars("shell.error_blacklisted", { table: error.message }))
|
|
103887
|
+
};
|
|
103055
103888
|
}
|
|
103056
103889
|
}
|
|
103057
103890
|
const startTime = Date.now();
|
|
@@ -103060,8 +103893,11 @@ class ReplEngine {
|
|
|
103060
103893
|
noLimit: this.state.noLimit
|
|
103061
103894
|
});
|
|
103062
103895
|
const elapsed = Date.now() - startTime;
|
|
103063
|
-
const
|
|
103064
|
-
const
|
|
103896
|
+
const fetched = result.rows;
|
|
103897
|
+
const fetchedColumns = fetched.length > 0 && fetched[0] ? Object.keys(fetched[0]) : [];
|
|
103898
|
+
const filtered = blacklistValidator ? blacklistValidator.filterColumnsForTables(referencedTables, fetched, fetchedColumns) : { filteredRows: fetched, omittedColumns: [] };
|
|
103899
|
+
const rows = filtered.filteredRows;
|
|
103900
|
+
const columnNames = fetchedColumns.filter((col) => !filtered.omittedColumns.includes(col));
|
|
103065
103901
|
const queryResult = {
|
|
103066
103902
|
rows,
|
|
103067
103903
|
rowCount: rows.length,
|
|
@@ -103113,10 +103949,6 @@ class ReplEngine {
|
|
|
103113
103949
|
}
|
|
103114
103950
|
return null;
|
|
103115
103951
|
}
|
|
103116
|
-
extractTableName(sql) {
|
|
103117
|
-
const match = sql.match(/\b(?:FROM|INTO|UPDATE)\s+["'`]?(\w+)["'`]?/i);
|
|
103118
|
-
return match?.[1];
|
|
103119
|
-
}
|
|
103120
103952
|
isConnectionError(error) {
|
|
103121
103953
|
const e = error;
|
|
103122
103954
|
const msg = (e.message ?? "").toLowerCase();
|
|
@@ -103131,6 +103963,9 @@ var init_repl_engine = __esm(() => {
|
|
|
103131
103963
|
init_permission_guard();
|
|
103132
103964
|
init_query_result_formatter();
|
|
103133
103965
|
init_message_loader();
|
|
103966
|
+
init_sql_tables();
|
|
103967
|
+
init_blacklist_validator();
|
|
103968
|
+
init_blacklist();
|
|
103134
103969
|
import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
103135
103970
|
});
|
|
103136
103971
|
|
|
@@ -103340,22 +104175,115 @@ function extractIndexFromPath(path6) {
|
|
|
103340
104175
|
return;
|
|
103341
104176
|
return seg.split("?")[0];
|
|
103342
104177
|
}
|
|
103343
|
-
|
|
103344
|
-
const
|
|
103345
|
-
|
|
103346
|
-
|
|
104178
|
+
function isUnscopedMetadataPath(path6) {
|
|
104179
|
+
const first = path6.replace(/^\//, "").split("/")[0]?.split("?")[0] ?? "";
|
|
104180
|
+
return UNSCOPED_METADATA_PREFIXES.includes(first);
|
|
104181
|
+
}
|
|
104182
|
+
function findIndexNamesInBody(body) {
|
|
104183
|
+
const found = [];
|
|
104184
|
+
const walk = (node) => {
|
|
104185
|
+
if (Array.isArray(node)) {
|
|
104186
|
+
for (const item of node)
|
|
104187
|
+
walk(item);
|
|
104188
|
+
return;
|
|
104189
|
+
}
|
|
104190
|
+
if (node === null || typeof node !== "object")
|
|
104191
|
+
return;
|
|
104192
|
+
for (const [key, value] of Object.entries(node)) {
|
|
104193
|
+
if (key === "_index" || key === "index") {
|
|
104194
|
+
for (const candidate of Array.isArray(value) ? value : [value]) {
|
|
104195
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
104196
|
+
found.push(candidate);
|
|
104197
|
+
}
|
|
104198
|
+
}
|
|
104199
|
+
walk(value);
|
|
104200
|
+
}
|
|
104201
|
+
};
|
|
104202
|
+
walk(body);
|
|
104203
|
+
return found;
|
|
104204
|
+
}
|
|
104205
|
+
async function runEsRequest(req, adapter, blacklistTables, blacklistColumns = {}) {
|
|
104206
|
+
const rawPath = req.path.split("?")[0] ?? req.path;
|
|
104207
|
+
const routedPath = normalizeEsPath(rawPath);
|
|
104208
|
+
const index = extractIndexFromPath(routedPath);
|
|
104209
|
+
if (blacklistTables.length > 0) {
|
|
104210
|
+
const literalSegments = `/${rawPath.split("/").filter(Boolean).join("/")}`;
|
|
104211
|
+
if (routedPath !== literalSegments) {
|
|
104212
|
+
throw new Error(`BlacklistRejection: '${req.path}' routes to '${routedPath}', which is not what it ` + `spells. Write the path the server will receive.`);
|
|
104213
|
+
}
|
|
104214
|
+
const blacklistedSegment = routedPath.split("/").find((segment) => segment.length > 0 && indexExpressionReaches(segment, blacklistTables));
|
|
104215
|
+
if (blacklistedSegment !== undefined) {
|
|
104216
|
+
throw new Error(`BlacklistRejection: index '${blacklistedSegment}' is blacklist-protected`);
|
|
104217
|
+
}
|
|
104218
|
+
if (index === undefined) {
|
|
104219
|
+
if (!isUnscopedMetadataPath(routedPath)) {
|
|
104220
|
+
throw new Error(`BlacklistRejection: '${req.path}' names no index, so it cannot be checked against ` + `the blacklist. Scope the request to an index, e.g. GET /<index>/_search.`);
|
|
104221
|
+
}
|
|
104222
|
+
}
|
|
104223
|
+
const inBody = findIndexNamesInBody(req.body).find((name2) => indexExpressionReaches(name2, blacklistTables));
|
|
104224
|
+
if (inBody !== undefined) {
|
|
104225
|
+
throw new Error(`BlacklistRejection: index '${inBody}' is blacklist-protected`);
|
|
104226
|
+
}
|
|
104227
|
+
}
|
|
104228
|
+
const protectedFields = new Set(Object.values(blacklistColumns).flat());
|
|
104229
|
+
if (protectedFields.size > 0) {
|
|
104230
|
+
const named = findStrings(req.body).find((text2) => protectedFields.has(text2));
|
|
104231
|
+
if (named !== undefined) {
|
|
104232
|
+
throw new Error(`BlacklistRejection: field '${named}' is blacklist-protected and cannot be named in a ` + `request \u2014 sorting, aggregating or scripting on it would return its values.`);
|
|
104233
|
+
}
|
|
103347
104234
|
}
|
|
103348
104235
|
let body = req.body;
|
|
103349
104236
|
if (req.path.includes("_search") && body !== null && typeof body === "object" && !Array.isArray(body) && body.size === undefined) {
|
|
103350
104237
|
body = { ...body, size: ES_SHELL_SIZE_CAP };
|
|
103351
104238
|
}
|
|
103352
|
-
|
|
104239
|
+
const response = await adapter.request(req.method, req.path, body);
|
|
104240
|
+
return protectedFields.size === 0 ? response : redactFields(response, protectedFields);
|
|
104241
|
+
}
|
|
104242
|
+
function findStrings(node) {
|
|
104243
|
+
const found = [];
|
|
104244
|
+
const walk = (value) => {
|
|
104245
|
+
if (typeof value === "string") {
|
|
104246
|
+
found.push(value);
|
|
104247
|
+
for (const piece of value.split(/[^A-Za-z0-9_.]+/)) {
|
|
104248
|
+
if (piece.length > 0 && piece !== value)
|
|
104249
|
+
found.push(piece);
|
|
104250
|
+
}
|
|
104251
|
+
return;
|
|
104252
|
+
}
|
|
104253
|
+
if (Array.isArray(value)) {
|
|
104254
|
+
for (const item of value)
|
|
104255
|
+
walk(item);
|
|
104256
|
+
return;
|
|
104257
|
+
}
|
|
104258
|
+
if (value === null || typeof value !== "object")
|
|
104259
|
+
return;
|
|
104260
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
104261
|
+
found.push(key);
|
|
104262
|
+
walk(nested);
|
|
104263
|
+
}
|
|
104264
|
+
};
|
|
104265
|
+
walk(node);
|
|
104266
|
+
return found;
|
|
104267
|
+
}
|
|
104268
|
+
function redactFields(node, fields) {
|
|
104269
|
+
if (Array.isArray(node))
|
|
104270
|
+
return node.map((item) => redactFields(item, fields));
|
|
104271
|
+
if (node === null || typeof node !== "object")
|
|
104272
|
+
return node;
|
|
104273
|
+
const out = {};
|
|
104274
|
+
for (const [key, value] of Object.entries(node)) {
|
|
104275
|
+
if (fields.has(key))
|
|
104276
|
+
continue;
|
|
104277
|
+
out[key] = redactFields(value, fields);
|
|
104278
|
+
}
|
|
104279
|
+
return out;
|
|
103353
104280
|
}
|
|
103354
104281
|
async function runEsShell(configPath) {
|
|
103355
104282
|
const config = await configModule.read(configPath);
|
|
103356
104283
|
const adapter = AdapterFactory.createElasticsearchAdapter(config.connection);
|
|
103357
104284
|
await adapter.connect();
|
|
103358
104285
|
const blacklistTables = config.blacklist?.tables ?? [];
|
|
104286
|
+
const blacklistColumns = config.blacklist?.columns ?? {};
|
|
103359
104287
|
console.error(import_picocolors4.default.bold("Elasticsearch shell \u2014 Kibana Dev Tools syntax"));
|
|
103360
104288
|
console.error(import_picocolors4.default.dim('Enter "<METHOD> /<path>" then an optional JSON body; submit with a blank line. Try: GET /_cat/indices'));
|
|
103361
104289
|
console.error(import_picocolors4.default.dim('Ctrl+C cancels the current block; Ctrl+D or "exit" quits.'));
|
|
@@ -103380,7 +104308,7 @@ async function runEsShell(configPath) {
|
|
|
103380
104308
|
}
|
|
103381
104309
|
try {
|
|
103382
104310
|
const req = parseEsRequest(block2);
|
|
103383
|
-
const res = await runEsRequest(req, adapter, blacklistTables);
|
|
104311
|
+
const res = await runEsRequest(req, adapter, blacklistTables, blacklistColumns);
|
|
103384
104312
|
console.log(JSON.stringify(res, null, 2));
|
|
103385
104313
|
} catch (error) {
|
|
103386
104314
|
console.error(import_picocolors4.default.red(error.message));
|
|
@@ -103408,11 +104336,13 @@ async function runEsShell(configPath) {
|
|
|
103408
104336
|
process.exit(0);
|
|
103409
104337
|
});
|
|
103410
104338
|
}
|
|
103411
|
-
var import_picocolors4, ES_SHELL_SIZE_CAP = 1000;
|
|
104339
|
+
var import_picocolors4, UNSCOPED_METADATA_PREFIXES, ES_SHELL_SIZE_CAP = 1000;
|
|
103412
104340
|
var init_es_shell = __esm(() => {
|
|
103413
104341
|
init_config();
|
|
103414
104342
|
init_adapters();
|
|
104343
|
+
init_es_index_target();
|
|
103415
104344
|
import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
104345
|
+
UNSCOPED_METADATA_PREFIXES = ["_cat", "_cluster", "_nodes", "_tasks", "_ingest", "_license"];
|
|
103416
104346
|
});
|
|
103417
104347
|
|
|
103418
104348
|
// src/core/repl/command-registry.ts
|
|
@@ -105380,6 +106310,10 @@ function fingerprintSql(sql) {
|
|
|
105380
106310
|
function shellEscapeDq(s) {
|
|
105381
106311
|
return s.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/`/g, "\\`").replace(/"/g, "\\\"");
|
|
105382
106312
|
}
|
|
106313
|
+
function explainSuggestions(sql) {
|
|
106314
|
+
const esc = shellEscapeDq(sql);
|
|
106315
|
+
return [`dbcli explain "${esc}"`, `dbcli guide missing-index-for "${esc}"`];
|
|
106316
|
+
}
|
|
105383
106317
|
function buildByFingerprint(events, slowMs, top) {
|
|
105384
106318
|
const errorByFp = new Map;
|
|
105385
106319
|
for (const e of events.filter(isErrored)) {
|
|
@@ -105448,11 +106382,7 @@ function buildByFingerprint(events, slowMs, top) {
|
|
|
105448
106382
|
stats.sort((a, b) => b.durationMs.total - a.durationMs.total);
|
|
105449
106383
|
return stats.map((s, i) => {
|
|
105450
106384
|
if (i < top && s.statement === "SELECT") {
|
|
105451
|
-
|
|
105452
|
-
return {
|
|
105453
|
-
...s,
|
|
105454
|
-
suggestedCommands: [`dbcli explain "${sql}"`, `dbcli guide missing-index-for "${sql}"`]
|
|
105455
|
-
};
|
|
106385
|
+
return { ...s, suggestedCommands: explainSuggestions(s.exampleSql) };
|
|
105456
106386
|
}
|
|
105457
106387
|
return s;
|
|
105458
106388
|
});
|
|
@@ -105479,13 +106409,27 @@ function buildErrors(events) {
|
|
|
105479
106409
|
message: e.error.message,
|
|
105480
106410
|
count: 0,
|
|
105481
106411
|
fingerprint: fingerprintSql(e.sql),
|
|
105482
|
-
exampleSql: e.sql
|
|
106412
|
+
exampleSql: e.sql,
|
|
106413
|
+
tables: e.tables
|
|
105483
106414
|
};
|
|
105484
106415
|
groups.set(key, g);
|
|
105485
106416
|
}
|
|
105486
106417
|
g.count += 1;
|
|
105487
106418
|
}
|
|
105488
|
-
return [...groups.values()].sort((a, b) => b.count - a.count)
|
|
106419
|
+
return [...groups.values()].sort((a, b) => b.count - a.count).map((g) => {
|
|
106420
|
+
const schemaCmds = g.tables.slice(0, MAX_ERROR_SCHEMA_TABLES).map((t2) => `dbcli schema ${t2}`);
|
|
106421
|
+
const hint = `failed ${g.count}\xD7: verify the involved table/column names against the live schema ` + `before fixing the SQL \u2014 never guess column names`;
|
|
106422
|
+
return {
|
|
106423
|
+
code: g.code,
|
|
106424
|
+
message: g.message,
|
|
106425
|
+
count: g.count,
|
|
106426
|
+
fingerprint: g.fingerprint,
|
|
106427
|
+
exampleSql: g.exampleSql,
|
|
106428
|
+
tables: g.tables,
|
|
106429
|
+
...schemaCmds.length ? { suggestedCommands: schemaCmds } : {},
|
|
106430
|
+
hints: [hint]
|
|
106431
|
+
};
|
|
106432
|
+
});
|
|
105489
106433
|
}
|
|
105490
106434
|
function buildHotTables(events) {
|
|
105491
106435
|
const map = new Map;
|
|
@@ -105514,10 +106458,13 @@ function buildRepetition(events, threshold) {
|
|
|
105514
106458
|
fingerprint: fp,
|
|
105515
106459
|
sessionId: e.sessionId,
|
|
105516
106460
|
tables: e.tables,
|
|
106461
|
+
statement: e.statement,
|
|
105517
106462
|
count: 0,
|
|
105518
106463
|
totalDurationMs: 0,
|
|
105519
106464
|
minTs: ts,
|
|
105520
|
-
maxTs: ts
|
|
106465
|
+
maxTs: ts,
|
|
106466
|
+
exampleSql: e.sql,
|
|
106467
|
+
exampleDuration: e.durationMs
|
|
105521
106468
|
};
|
|
105522
106469
|
groups.set(key, g);
|
|
105523
106470
|
}
|
|
@@ -105527,15 +106474,27 @@ function buildRepetition(events, threshold) {
|
|
|
105527
106474
|
g.minTs = ts;
|
|
105528
106475
|
if (ts > g.maxTs)
|
|
105529
106476
|
g.maxTs = ts;
|
|
106477
|
+
if (e.durationMs > g.exampleDuration) {
|
|
106478
|
+
g.exampleDuration = e.durationMs;
|
|
106479
|
+
g.exampleSql = e.sql;
|
|
106480
|
+
}
|
|
105530
106481
|
}
|
|
105531
|
-
return [...groups.values()].filter((g) => g.count >= threshold).map((g) =>
|
|
105532
|
-
|
|
105533
|
-
|
|
105534
|
-
|
|
105535
|
-
|
|
105536
|
-
|
|
105537
|
-
|
|
105538
|
-
|
|
106482
|
+
return [...groups.values()].filter((g) => g.count >= threshold).sort((a, b) => b.count - a.count).map((g) => {
|
|
106483
|
+
const spanMs = g.maxTs - g.minTs;
|
|
106484
|
+
const hint = `N+1 suspect: the same query ran ${g.count}\xD7 within one session over ${spanMs}ms \u2014 ` + `collapse into a single query (JOIN / IN (...)) or cache the result`;
|
|
106485
|
+
return {
|
|
106486
|
+
fingerprint: g.fingerprint,
|
|
106487
|
+
sessionId: g.sessionId,
|
|
106488
|
+
count: g.count,
|
|
106489
|
+
spanMs,
|
|
106490
|
+
totalDurationMs: g.totalDurationMs,
|
|
106491
|
+
tables: g.tables,
|
|
106492
|
+
statement: g.statement,
|
|
106493
|
+
exampleSql: g.exampleSql,
|
|
106494
|
+
...g.statement === "SELECT" ? { suggestedCommands: explainSuggestions(g.exampleSql) } : {},
|
|
106495
|
+
hints: [hint]
|
|
106496
|
+
};
|
|
106497
|
+
});
|
|
105539
106498
|
}
|
|
105540
106499
|
function analyzeEvents(events, opts) {
|
|
105541
106500
|
const timestamps = events.map((e) => e.timestamp).filter(Boolean).sort();
|
|
@@ -105589,7 +106548,7 @@ function buildSummary(events, slowMs) {
|
|
|
105589
106548
|
}
|
|
105590
106549
|
};
|
|
105591
106550
|
}
|
|
105592
|
-
var isCompleted = (e) => e.type === "query_completed", isErrored = (e) => e.type === "query_errored";
|
|
106551
|
+
var isCompleted = (e) => e.type === "query_completed", isErrored = (e) => e.type === "query_errored", MAX_ERROR_SCHEMA_TABLES = 3;
|
|
105593
106552
|
var init_analyze = __esm(() => {
|
|
105594
106553
|
init_sql_metadata();
|
|
105595
106554
|
});
|
|
@@ -105629,12 +106588,29 @@ function renderAnalysisText(report, top) {
|
|
|
105629
106588
|
for (const r of report.repetition.slice(0, top)) {
|
|
105630
106589
|
L2.push(` ${r.count}x in session ${r.sessionId} (${r.spanMs}ms) ${r.fingerprint}`);
|
|
105631
106590
|
}
|
|
105632
|
-
const cmds = [
|
|
106591
|
+
const cmds = [
|
|
106592
|
+
...new Set([
|
|
106593
|
+
...report.byFingerprint.flatMap((f) => f.suggestedCommands ?? []),
|
|
106594
|
+
...report.errors.flatMap((e) => e.suggestedCommands ?? []),
|
|
106595
|
+
...report.repetition.flatMap((r) => r.suggestedCommands ?? [])
|
|
106596
|
+
])
|
|
106597
|
+
];
|
|
105633
106598
|
if (cmds.length) {
|
|
105634
106599
|
L2.push("", "SUGGESTED COMMANDS");
|
|
105635
106600
|
for (const c2 of cmds)
|
|
105636
106601
|
L2.push(` ${c2}`);
|
|
105637
106602
|
}
|
|
106603
|
+
const hints = [
|
|
106604
|
+
...new Set([
|
|
106605
|
+
...report.errors.flatMap((e) => e.hints ?? []),
|
|
106606
|
+
...report.repetition.flatMap((r) => r.hints ?? [])
|
|
106607
|
+
])
|
|
106608
|
+
];
|
|
106609
|
+
if (hints.length) {
|
|
106610
|
+
L2.push("", "HINTS");
|
|
106611
|
+
for (const h of hints)
|
|
106612
|
+
L2.push(` ${h}`);
|
|
106613
|
+
}
|
|
105638
106614
|
return L2.join(`
|
|
105639
106615
|
`);
|
|
105640
106616
|
}
|