fix(mongodb): honor current command execution mode (#2702)
This commit is contained in:
1 parent
18e5df6f5e
commit
d7e2eac307
3 files changed
+55
-7
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
import * as api from "@/lib/backend/api";
|
import * as api from "@/lib/backend/api";
|
||||||
|
import { mongoCommandRangeAtCursor } from "@/lib/sql/sqlStatementRanges";
|
||||||
import type { DatabaseType } from "@/types/database";
|
import type { DatabaseType } from "@/types/database";
|
||||||
|
|
||||||
export type ExecuteMode = "all" | "current";
|
export type ExecuteMode = "all" | "current";
|
||||||
@@ -52,10 +53,12 @@ export async function resolveExecutableSqlWithBackend(fullSql: string, selectedS
|
|||||||
const trimmedSelection = selectedSql.trim();
|
const trimmedSelection = selectedSql.trim();
|
||||||
if (trimmedSelection) return trimmedSelection;
|
if (trimmedSelection) return trimmedSelection;
|
||||||
|
|
||||||
// MongoDB uses dedicated per-command gutter actions for "current command";
|
if (options?.databaseType === "mongodb") {
|
||||||
// the main editor execute action keeps its long-standing "run all text"
|
if (options.mode === "current" && options.cursorPos !== undefined) {
|
||||||
// behavior when nothing is selected.
|
return mongoCommandRangeAtCursor(fullSql, options.cursorPos)?.sql ?? fullSql;
|
||||||
if (options?.databaseType === "mongodb") return fullSql;
|
}
|
||||||
|
return fullSql;
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.mode === "current" && options.cursorPos !== undefined) {
|
if (options?.mode === "current" && options.cursorPos !== undefined) {
|
||||||
return await api.findStatementAtCursor(fullSql, options.cursorPos, options.databaseType);
|
return await api.findStatementAtCursor(fullSql, options.cursorPos, options.databaseType);
|
||||||
|
|||||||
@@ -409,6 +409,26 @@ export function statementRangeAtCursor(sql: string, cursorPos: number, databaseT
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mongoCommandRangeAtCursor(sql: string, cursorPos: number): SqlTextRange | null {
|
||||||
|
const pos = clampCursor(sql, cursorPos);
|
||||||
|
if (isCursorOnBlankLine(sql, pos)) return null;
|
||||||
|
|
||||||
|
const commands = splitMongoCommandRanges(sql);
|
||||||
|
for (let index = 0; index < commands.length; index += 1) {
|
||||||
|
const command = commands[index];
|
||||||
|
const range = { from: command.from, to: command.to, sql: command.text };
|
||||||
|
|
||||||
|
if (pos >= command.from && pos <= command.to) return range;
|
||||||
|
|
||||||
|
const next = commands[index + 1];
|
||||||
|
if (pos > command.to && (!next || pos < next.from) && isCursorInSameLineDelimiterGap(sql, command.to, pos)) return range;
|
||||||
|
|
||||||
|
if (pos < command.from && sql.slice(pos, command.from).trim() === "" && isCursorOnStatementLine(sql, pos, command)) return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function isCursorInSameLineDelimiterGap(sql: string, previousStatementEnd: number, cursorPos: number): boolean {
|
function isCursorInSameLineDelimiterGap(sql: string, previousStatementEnd: number, cursorPos: number): boolean {
|
||||||
if (cursorPos <= previousStatementEnd) return false;
|
if (cursorPos <= previousStatementEnd) return false;
|
||||||
const between = sql.slice(previousStatementEnd, cursorPos);
|
const between = sql.slice(previousStatementEnd, cursorPos);
|
||||||
@@ -1450,7 +1470,7 @@ function isCursorOnBlankLine(sql: string, pos: number): boolean {
|
|||||||
return sql.slice(lineStart, lineEnd).trim() === "";
|
return sql.slice(lineStart, lineEnd).trim() === "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCursorOnStatementLine(sql: string, pos: number, statement: RawStatement): boolean {
|
function isCursorOnStatementLine(sql: string, pos: number, statement: Pick<RawStatement, "from">): boolean {
|
||||||
const lineStart = sql.lastIndexOf("\n", pos - 1) + 1;
|
const lineStart = sql.lastIndexOf("\n", pos - 1) + 1;
|
||||||
let lineEnd = sql.indexOf("\n", pos);
|
let lineEnd = sql.indexOf("\n", pos);
|
||||||
if (lineEnd === -1) lineEnd = sql.length;
|
if (lineEnd === -1) lineEnd = sql.length;
|
||||||
|
|||||||
@@ -13,20 +13,45 @@ beforeEach(() => {
|
|||||||
apiMock.findStatementAtCursor.mockReset();
|
apiMock.findStatementAtCursor.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("mongodb backend resolution keeps the full text when nothing is selected", async () => {
|
test("mongodb backend resolution uses the current command when configured", async () => {
|
||||||
apiMock.findStatementAtCursor.mockResolvedValue("db.users.insertOne({ name: 'ignored' })");
|
apiMock.findStatementAtCursor.mockResolvedValue("db.users.insertOne({ name: 'ignored' })");
|
||||||
|
|
||||||
const fullSql = "db.users.insertOne({ name: 'Ada' });\ndb.users.insertOne({ name: 'Grace' });";
|
const fullSql = 'db.users.find({ name: "Ada" });\ndb.users.find({ name: "Grace" });\ndb.users.find({ name: "Linus" });';
|
||||||
const resolved = await resolveExecutableSqlWithBackend(fullSql, "", {
|
const resolved = await resolveExecutableSqlWithBackend(fullSql, "", {
|
||||||
mode: "current",
|
mode: "current",
|
||||||
cursorPos: fullSql.indexOf("Grace"),
|
cursorPos: fullSql.indexOf("Grace"),
|
||||||
databaseType: "mongodb",
|
databaseType: "mongodb",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assert.equal(resolved, 'db.users.find({ name: "Grace" })');
|
||||||
|
assert.equal(apiMock.findStatementAtCursor.mock.calls.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mongodb backend resolution keeps the full text in all mode", async () => {
|
||||||
|
const fullSql = 'db.users.find({ name: "Ada" });\ndb.users.find({ name: "Grace" });';
|
||||||
|
const resolved = await resolveExecutableSqlWithBackend(fullSql, "", {
|
||||||
|
mode: "all",
|
||||||
|
cursorPos: fullSql.indexOf("Grace"),
|
||||||
|
databaseType: "mongodb",
|
||||||
|
});
|
||||||
|
|
||||||
assert.equal(resolved, fullSql);
|
assert.equal(resolved, fullSql);
|
||||||
assert.equal(apiMock.findStatementAtCursor.mock.calls.length, 0);
|
assert.equal(apiMock.findStatementAtCursor.mock.calls.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mongodb backend resolution prefers the manual selection", async () => {
|
||||||
|
const fullSql = 'db.users.find({ name: "Ada" });\ndb.users.find({ name: "Grace" });';
|
||||||
|
const selectedSql = 'db.users.find({ name: "Ada" })';
|
||||||
|
const resolved = await resolveExecutableSqlWithBackend(fullSql, selectedSql, {
|
||||||
|
mode: "current",
|
||||||
|
cursorPos: fullSql.indexOf("Grace"),
|
||||||
|
databaseType: "mongodb",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(resolved, selectedSql);
|
||||||
|
assert.equal(apiMock.findStatementAtCursor.mock.calls.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
test("non-mongodb backend resolution still asks the backend for the current statement", async () => {
|
test("non-mongodb backend resolution still asks the backend for the current statement", async () => {
|
||||||
apiMock.findStatementAtCursor.mockResolvedValue("SELECT 2");
|
apiMock.findStatementAtCursor.mockResolvedValue("SELECT 2");
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user