fix(editor): run hinted SQL from statement gutter

Closes #5986
This commit is contained in:
t8y2 committed 2026-08-12 20:35:26 +00:00
1 parent 3dd034bf06
commit 5375bd1473
2 files changed
+75 -1

No files matched your search

@@ -67,6 +67,39 @@ describe("executableStatementRangeCacheForDoc", () => {
expect(executableStatementRangeStartingAt(cache, doc.line(5).from)).toBeNull();
});
it.each(["/*& tenant:'gdx' */", "/*&tenant:mctest*/", "/*+ MAX_EXECUTION_TIME(1000) */", "/*@global:true*/", "/*proxy*/"])("resolves the SQL line after a preserved leading directive for gutter execution: %s", (directive) => {
const sql = `SELECT 0;\n${directive}\nSELECT 1;`;
const doc = Text.of(sql.split("\n"));
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
expect(executableStatementRangeStartingAt(cache, doc.line(3).from)?.sql).toBe(`${directive}\nSELECT 1`);
});
it("keeps a same-line tenant hint in the gutter execution range", () => {
const sql = "/*& tenant:'gdx' */ SELECT\n*\nFROM table";
const doc = Text.of(sql.split("\n"));
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
expect(executableStatementRangeStartingAt(cache, doc.line(1).from)?.sql).toBe(sql);
});
it("treats a SQL Server temporary table after a hint as executable content", () => {
const sql = "/*+ hint */\n#temporary_table";
const doc = Text.of(sql.split("\n"));
const cache = executableStatementRangeCacheForDoc(null, doc, "sqlserver");
expect(executableStatementRangeStartingAt(cache, doc.line(2).from)?.sql).toBe(sql);
});
it("does not attach ordinary leading block comments to gutter execution", () => {
const doc = Text.of(["/* comment */", "SELECT 1;", "/* comment only */"]);
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
expect(executableStatementRangeStartingAt(cache, doc.line(1).from)).toBeNull();
expect(executableStatementRangeStartingAt(cache, doc.line(2).from)?.sql).toBe("SELECT 1");
expect(executableStatementRangeStartingAt(cache, doc.line(3).from)).toBeNull();
});
it("does not resolve gutter run buttons when non-whitespace precedes the statement on the same line", () => {
const doc = Text.of(["/* comment */ SELECT 1;"]);
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
@@ -30,17 +30,58 @@ export function executableStatementRangeCacheForDoc(
const byStart = new Map<number, SqlTextRange>();
const byExecutableLineStart = new Map<number, SqlTextRange>();
const ranges = parse(doc.toString(), databaseType, parameterOptions);
const sql = doc.toString();
const ranges = parse(sql, databaseType, parameterOptions);
for (const range of ranges) {
byStart.set(range.from, range);
const line = doc.lineAt(range.from);
if (doc.sliceString(line.from, range.from).trim() === "") {
byExecutableLineStart.set(line.from, range);
}
const executableStart = executableStartAfterLeadingDirective(sql, range, databaseType, parameterOptions);
if (executableStart !== null) {
byExecutableLineStart.set(doc.lineAt(executableStart).from, range);
}
}
return { doc, databaseType, parameterOptions, parameterSyntaxKey, byStart, byExecutableLineStart, ranges };
}
function executableStartAfterLeadingDirective(sql: string, range: SqlTextRange, databaseType?: DatabaseType, parameterOptions?: SqlParameterOptions): number | null {
const text = range.sql;
const hasExecutableDirective = text.startsWith("/*+") || text.startsWith("/*@") || text.startsWith("/*&") || (databaseType === "mysql" && text.startsWith("/*proxy*/"));
if (!hasExecutableDirective) return null;
let offset = 0;
while (offset < text.length) {
while (/\s/.test(text[offset] ?? "")) offset += 1;
if (text.startsWith("/*", offset)) {
const close = text.indexOf("*/", offset + 2);
if (close < 0) return null;
offset = close + 2;
continue;
}
if (text.startsWith("--", offset)) {
const newline = text.indexOf("\n", offset + 2);
if (newline < 0) return null;
offset = newline + 1;
continue;
}
if (databaseType !== "sqlserver" && text[offset] === "#" && readSqlBracedParameterAt(sql, range.from + offset, parameterOptions)?.syntax !== "mybatis") {
const newline = text.indexOf("\n", offset + 1);
if (newline < 0) return null;
offset = newline + 1;
continue;
}
return offset < text.length ? range.from + offset : null;
}
return null;
}
export function executableStatementRangeStartingAt(cache: ExecutableStatementRangeCache, lineFrom: number): SqlTextRange | null {
return cache.byStart.get(lineFrom) ?? cache.byExecutableLineStart.get(lineFrom) ?? null;
}