fix(mongo): parse newline chained find calls
This commit is contained in:
parent
b89df5a43c
commit
bc49bbbbfa
|
|
@ -305,19 +305,19 @@ function parseFindTarget(source: string): { collection: string; findCallIndex: n
|
|||
|
||||
function parseCollectionMethodTarget(source: string, method: string): { collection: string; methodCallIndex: number } | null {
|
||||
const escapedMethod = escapeRegExp(method);
|
||||
const direct = new RegExp(`^db\\.([A-Za-z_$][\\w$]*)\\.${escapedMethod}\\s*\\(`).exec(source);
|
||||
const direct = new RegExp(`^db\\s*\\.\\s*([A-Za-z_$][\\w$]*)\\s*\\.\\s*${escapedMethod}\\s*\\(`).exec(source);
|
||||
if (direct) {
|
||||
return {
|
||||
collection: direct[1],
|
||||
methodCallIndex: source.indexOf(`.${method}`, direct[0].length - `.${method}(`.length),
|
||||
methodCallIndex: findChainedMethodCallIndex(source, method),
|
||||
};
|
||||
}
|
||||
|
||||
const getCollection = new RegExp(`^db\\.getCollection\\s*\\(\\s*(["'])(.*?)\\1\\s*\\)\\.${escapedMethod}\\s*\\(`).exec(source);
|
||||
const getCollection = new RegExp(`^db\\s*\\.\\s*getCollection\\s*\\(\\s*(["'])(.*?)\\1\\s*\\)\\s*\\.\\s*${escapedMethod}\\s*\\(`).exec(source);
|
||||
if (getCollection) {
|
||||
return {
|
||||
collection: getCollection[2],
|
||||
methodCallIndex: source.indexOf(`.${method}`, getCollection[0].length - `.${method}(`.length),
|
||||
methodCallIndex: findChainedMethodCallIndex(source, method),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -444,24 +444,23 @@ function readChainedIntegerArgument(source: string, name: string, fallback: numb
|
|||
}
|
||||
|
||||
function readChainedCallArgument(source: string, name: string): string | undefined {
|
||||
const call = `.${name}`;
|
||||
let index = source.indexOf(call);
|
||||
while (index >= 0) {
|
||||
const afterName = index + call.length;
|
||||
const openIndex = skipWhitespace(source, afterName);
|
||||
if (source[openIndex] === "(") {
|
||||
const closeIndex = findMatchingParen(source, openIndex);
|
||||
if (closeIndex >= 0) return source.slice(openIndex + 1, closeIndex);
|
||||
}
|
||||
index = source.indexOf(call, afterName);
|
||||
const pattern = chainedMethodCallPattern(name);
|
||||
let match = pattern.exec(source);
|
||||
while (match) {
|
||||
const openIndex = source.indexOf("(", match.index);
|
||||
const closeIndex = findMatchingParen(source, openIndex);
|
||||
if (closeIndex >= 0) return source.slice(openIndex + 1, closeIndex);
|
||||
match = pattern.exec(source);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function skipWhitespace(source: string, index: number) {
|
||||
let cursor = index;
|
||||
while (/\s/.test(source[cursor] || "")) cursor += 1;
|
||||
return cursor;
|
||||
function findChainedMethodCallIndex(source: string, name: string): number {
|
||||
return chainedMethodCallPattern(name).exec(source)?.index ?? -1;
|
||||
}
|
||||
|
||||
function chainedMethodCallPattern(name: string): RegExp {
|
||||
return new RegExp(`\\.\\s*${escapeRegExp(name)}\\s*\\(`, "g");
|
||||
}
|
||||
|
||||
function splitTopLevel(source: string): string[] {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,22 @@ test("parseMongoFindCommand parses getCollection find with chained sort skip and
|
|||
});
|
||||
});
|
||||
|
||||
test("parseMongoFindCommand accepts line breaks before find and chained calls", () => {
|
||||
const command = parseMongoFindCommand(`db.getCollection("accounting_reconciliations")
|
||||
.find({
|
||||
"_id": ObjectId("68ad51ca84c8127bc7d44cb3")
|
||||
})
|
||||
.sort({ lineNo: -1 })
|
||||
.skip(5)
|
||||
.limit(20)`);
|
||||
assert.ok(command);
|
||||
assert.equal(command.collection, "accounting_reconciliations");
|
||||
assert.deepEqual(JSON.parse(command.filter), { _id: { $oid: "68ad51ca84c8127bc7d44cb3" } });
|
||||
assert.deepEqual(JSON.parse(command.sort || "{}"), { lineNo: -1 });
|
||||
assert.equal(command.skip, 5);
|
||||
assert.equal(command.limit, 20);
|
||||
});
|
||||
|
||||
test("parseMongoFindCommand accepts Compass-style unquoted keys and ObjectId", () => {
|
||||
const command = parseMongoFindCommand("db.products.find({_id: ObjectId('6a045a92d2971e44243771a1')}).limit(1)");
|
||||
assert.ok(command);
|
||||
|
|
|
|||
|
|
@ -921,10 +921,11 @@ export function evaluateMongoAggregateSafety(command: MongoAggregateCommand, opt
|
|||
}
|
||||
|
||||
function parseCollectionMethodTarget(source: string, method: string): { collection: string; methodCallIndex: number } | null {
|
||||
const direct = new RegExp(`^db\\.([A-Za-z_$][\\w$]*)\\.${method}\\s*\\(`).exec(source);
|
||||
if (direct) return { collection: direct[1], methodCallIndex: source.indexOf(`.${method}`) };
|
||||
const quoted = new RegExp(`^db\\.getCollection\\(\\s*(['"])([^'"]+)\\1\\s*\\)\\.${method}\\s*\\(`).exec(source);
|
||||
if (quoted) return { collection: quoted[2], methodCallIndex: source.indexOf(`.${method}`) };
|
||||
const escapedMethod = escapeRegExp(method);
|
||||
const direct = new RegExp(`^db\\s*\\.\\s*([A-Za-z_$][\\w$]*)\\s*\\.\\s*${escapedMethod}\\s*\\(`).exec(source);
|
||||
if (direct) return { collection: direct[1], methodCallIndex: findChainedMethodCallIndex(source, method) };
|
||||
const quoted = new RegExp(`^db\\s*\\.\\s*getCollection\\s*\\(\\s*(['"])([^'"]+)\\1\\s*\\)\\s*\\.\\s*${escapedMethod}\\s*\\(`).exec(source);
|
||||
if (quoted) return { collection: quoted[2], methodCallIndex: findChainedMethodCallIndex(source, method) };
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -936,14 +937,25 @@ function parseMethodArgs(source: string, methodCallIndex: number): string[] | nu
|
|||
}
|
||||
|
||||
function readChainedCallArgument(chain: string, method: string): string | undefined {
|
||||
const pattern = new RegExp(`\\.${method}\\s*\\(`, "g");
|
||||
const match = pattern.exec(chain);
|
||||
const match = chainedMethodCallPattern(method).exec(chain);
|
||||
if (!match) return undefined;
|
||||
const openIndex = match.index + match[0].lastIndexOf("(");
|
||||
const openIndex = chain.indexOf("(", match.index);
|
||||
const closeIndex = findMatchingParen(chain, openIndex);
|
||||
return closeIndex < 0 ? undefined : chain.slice(openIndex + 1, closeIndex);
|
||||
}
|
||||
|
||||
function findChainedMethodCallIndex(source: string, method: string): number {
|
||||
return chainedMethodCallPattern(method).exec(source)?.index ?? -1;
|
||||
}
|
||||
|
||||
function chainedMethodCallPattern(method: string): RegExp {
|
||||
return new RegExp(`\\.\\s*${escapeRegExp(method)}\\s*\\(`, "g");
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function readChainedIntegerArgument(chain: string, method: string, fallback: number): number | null {
|
||||
const arg = readChainedCallArgument(chain, method);
|
||||
if (arg === undefined) return fallback;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,22 @@ test("parseMongoFindCommand accepts shell-style find commands", () => {
|
|||
});
|
||||
});
|
||||
|
||||
test("parseMongoFindCommand accepts line breaks before find and chained calls", () => {
|
||||
const command = parseMongoFindCommand(`db.getCollection("operation_logs")
|
||||
.find({
|
||||
"_id": ObjectId("68ad51ca84c8127bc7d44cb3")
|
||||
})
|
||||
.sort({ ts: -1 })
|
||||
.skip(5)
|
||||
.limit(10)`);
|
||||
assert.ok(command);
|
||||
assert.equal(command.collection, "operation_logs");
|
||||
assert.deepEqual(JSON.parse(command.filter), { _id: { $oid: "68ad51ca84c8127bc7d44cb3" } });
|
||||
assert.deepEqual(JSON.parse(command.sort || "{}"), { ts: -1 });
|
||||
assert.equal(command.skip, 5);
|
||||
assert.equal(command.limit, 10);
|
||||
});
|
||||
|
||||
test("parseMongoFindCommand accepts Compass-style unquoted keys and ObjectId", () => {
|
||||
const command = parseMongoFindCommand("db.products.find({_id: ObjectId('6a045a92d2971e44243771a1')}).limit(1)");
|
||||
assert.ok(command);
|
||||
|
|
|
|||
Loading…
Reference in New Issue