fix(sql): preserve same-line TDSQL directives

This commit is contained in:
李子凡 authored and GitHub committed 2026-08-14 22:46:32 +08:00
1 parent a574459f8b
commit 5695ef6e77
6 files changed
+145 -39

No files matched your search

@@ -118,12 +118,12 @@ describe("executableStatementRangeCacheForDoc", () => {
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;"]);
it("resolves a same-line leading TDSQL directive for gutter execution", () => {
const sql = "/*sets:allsets */ SELECT 1;";
const doc = Text.of([sql]);
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
expect(executableStatementRangeStartingAt(cache, doc.line(1).from)).toBeNull();
expect(executableStatementRangeStartingAt(cache, doc.toString().indexOf("SELECT"))?.sql).toBe("SELECT 1");
expect(executableStatementRangeStartingAt(cache, doc.line(1).from)?.sql).toBe(sql.slice(0, -1));
});
it("resolves the current statement from a cursor inside a continuation line", () => {
@@ -205,12 +205,13 @@ describe("executableStatementRangeCacheForDoc", () => {
expect(executableStatementRangeAtCursor(cache, doc.line(4).from)).toBeNull();
});
it("resolves SQL after a leading block comment on the same line", () => {
const doc = Text.of(["/* comment */ SELECT 1;"]);
it("preserves a same-line TDSQL directive while ignoring a cursor inside its comment", () => {
const sql = "/*sets:allsets */ SELECT 1;";
const doc = Text.of([sql]);
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
expect(executableStatementRangeAtCursor(cache, doc.toString().indexOf("SELECT"))?.sql).toBe("SELECT 1");
expect(executableStatementRangeAtCursor(cache, doc.toString().indexOf("comment"))).toBeNull();
expect(executableStatementRangeAtCursor(cache, sql.indexOf("SELECT"))?.sql).toBe(sql.slice(0, -1));
expect(executableStatementRangeAtCursor(cache, sql.indexOf("sets"))).toBeNull();
});
it("rebuilds the cache when the document instance changes", () => {
@@ -19,6 +19,12 @@ describe("resolveExecutableSql", () => {
expect(resolveExecutableSql("SELECT 1;", selectedSql, { mode: "current", cursorPos: 0 })).toBe(selectedSql);
});
it("keeps a manually selected same-line TDSQL directive unchanged", () => {
const selectedSql = "/*sets:allsets */ SELECT count(*) FROM tenant_table";
expect(resolveExecutableSql("SELECT 1;", selectedSql, { mode: "current", cursorPos: 0 })).toBe(selectedSql);
});
});
describe("executionCandidateForMode", () => {
@@ -1263,6 +1263,36 @@ describe("buildExecutionCandidates", () => {
expect(splitSqlStatementRanges("/*proxy*/", "mysql")).toEqual([]);
});
it.each(["/*sets:allsets */", "/*master*/", "/*slave:set_1781591902_7*/", "/*future-route:anywhere*/"])("preserves a same-line TDSQL directive without relying on a keyword allowlist: %s", (directive) => {
const directedSql = `${directive} SELECT count(*) FROM tenant_table`;
const sql = `SELECT 1;\n${directedSql};\nSELECT 2;`;
const candidates = buildExecutionCandidates(sql, indexOf(sql, "tenant_table"), "mysql");
expect(executionCandidateForMode(candidates, "current")?.sql).toBe(directedSql);
expect(rangeSqlTexts(executableStatementRanges(sql, "mysql"))).toEqual(["SELECT 1", directedSql, "SELECT 2"]);
});
it("handles long same-line directive chains without rescanning growing prefixes", () => {
const prefix = "/**/".repeat(80_000);
const sql = `${prefix} SELECT 1`;
expect(splitSqlStatementRanges(sql, "mysql")[0]?.sql).toBe(sql);
});
it("does not preserve a generic TDSQL-style directive on a separate line", () => {
const sql = "/*sets:allsets */\nSELECT count(*) FROM tenant_table";
const candidates = buildExecutionCandidates(sql, indexOf(sql, "tenant_table"), "mysql");
expect(executionCandidateForMode(candidates, "current")?.sql).toBe("SELECT count(*) FROM tenant_table");
});
it("does not preserve a same-line TDSQL-style directive for other database types", () => {
const sql = "/*sets:allsets */ SELECT count(*) FROM tenant_table";
const candidates = buildExecutionCandidates(sql, indexOf(sql, "tenant_table"), "postgres");
expect(executionCandidateForMode(candidates, "current")?.sql).toBe("SELECT count(*) FROM tenant_table");
});
it.each(["/* ordinary */", "/*unknown*/", "/* proxy */", "/*PROXY*/"])("keeps %s as a non-executable leading comment", (comment) => {
const sql = `${comment}\nSHOW PROXY STATUS`;
const candidates = buildExecutionCandidates(sql, indexOf(sql, "STATUS"), "mysql");
+32 -8
View File
@@ -301,7 +301,8 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
let statementEnd = -1;
let statementHitStart = 0;
let pendingHintStart = -1;
let pendingProxyDirectiveStart = -1;
let pendingMysqlDirectiveStart = -1;
let pendingMysqlDirectiveLineEnd = -1;
let customDelimiter: string | null = null;
let state: QuoteState = "none";
let dollarTag = "";
@@ -310,12 +311,23 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
let i = 0;
const isWhitespace = (ch: string) => ch === " " || ch === "\t" || ch === "\r" || ch === "\n";
const nextLineBreak = (from: number) => {
const carriageReturn = sql.indexOf("\r", from);
const lineFeed = sql.indexOf("\n", from);
if (carriageReturn === -1) return lineFeed === -1 ? len : lineFeed;
if (lineFeed === -1) return carriageReturn;
return Math.min(carriageReturn, lineFeed);
};
const markContent = (pos: number) => {
if (statementStart === -1) {
statementStart = pendingProxyDirectiveStart !== -1 ? pendingProxyDirectiveStart : pendingHintStart === -1 ? pos : pendingHintStart;
// TDSQL accepts arbitrary leading block directives on the SQL line. The
// exact /*proxy*/ directive also keeps its historical multiline support.
const directiveStart = pendingMysqlDirectiveStart !== -1 && (sql.startsWith("/*proxy*/", pendingMysqlDirectiveStart) || pos < pendingMysqlDirectiveLineEnd) ? pendingMysqlDirectiveStart : -1;
statementStart = directiveStart !== -1 ? directiveStart : pendingHintStart === -1 ? pos : pendingHintStart;
pendingHintStart = -1;
pendingProxyDirectiveStart = -1;
pendingMysqlDirectiveStart = -1;
pendingMysqlDirectiveLineEnd = -1;
}
statementEnd = pos + 1;
};
@@ -324,7 +336,8 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
if (statementStart === -1) {
statementEnd = -1;
pendingHintStart = -1;
pendingProxyDirectiveStart = -1;
pendingMysqlDirectiveStart = -1;
pendingMysqlDirectiveLineEnd = -1;
postgresDollarQuotedRoutine = false;
oraclePlSqlStatementEnd = undefined;
return;
@@ -336,7 +349,8 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
statementStart = -1;
statementEnd = -1;
pendingHintStart = -1;
pendingProxyDirectiveStart = -1;
pendingMysqlDirectiveStart = -1;
pendingMysqlDirectiveLineEnd = -1;
postgresDollarQuotedRoutine = false;
oraclePlSqlStatementEnd = undefined;
};
@@ -441,13 +455,15 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
// Line comments consume up to (and including) the newline.
if (ch === "-" && next === "-") {
pendingProxyDirectiveStart = -1;
pendingMysqlDirectiveStart = -1;
pendingMysqlDirectiveLineEnd = -1;
const newline = sql.indexOf("\n", i);
i = newline === -1 ? len : newline + 1;
continue;
}
if (startsHashLineComment(sql, i, databaseType, parameterOptions)) {
pendingProxyDirectiveStart = -1;
pendingMysqlDirectiveStart = -1;
pendingMysqlDirectiveLineEnd = -1;
const newline = sql.indexOf("\n", i);
i = newline === -1 ? len : newline + 1;
continue;
@@ -458,7 +474,15 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
if (statementStart === -1 && pendingHintStart === -1 && (hintMarker === "+" || hintMarker === "@" || hintMarker === "&")) pendingHintStart = i;
const close = sql.indexOf("*/", i + 2);
if (statementStart === -1) {
pendingProxyDirectiveStart = databaseType === "mysql" && sql.startsWith("/*proxy*/", i) ? i : -1;
if (databaseType === "mysql") {
if (pendingMysqlDirectiveStart === -1 || i > pendingMysqlDirectiveLineEnd) {
pendingMysqlDirectiveStart = i;
pendingMysqlDirectiveLineEnd = nextLineBreak(i);
}
} else {
pendingMysqlDirectiveStart = -1;
pendingMysqlDirectiveLineEnd = -1;
}
}
i = close === -1 ? len : close + 2;
continue;