fix(sql): keep Ctrl+Enter on current statement at trailing whitespace
This commit is contained in:
1 parent
c32f92a902
commit
b437c470e1
4 files changed
+177
-4
No files matched your search
@@ -103,6 +103,40 @@ describe("executableStatementRangeCacheForDoc", () => {
|
||||
expect(executableStatementRangeAtCursor(cache, delimiterCursor + 1)?.sql).toBe("SELECT *\nFROM users");
|
||||
});
|
||||
|
||||
it("resolves a trailing-whitespace cursor on a multi-line statement tail to that statement", () => {
|
||||
const sql = "WITH x AS (SELECT 1)\nSELECT 2 \nSELECT 3;";
|
||||
const doc = Text.of(sql.split("\n"));
|
||||
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
|
||||
expect(executableStatementRangeAtCursor(cache, contentEnd)?.sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
expect(executableStatementRangeAtCursor(cache, contentEnd + 1)?.sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
expect(executableStatementRangeAtCursor(cache, contentEnd + 2)?.sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
});
|
||||
|
||||
it("keeps the simple single-line trailing-whitespace case on the current statement", () => {
|
||||
const sql = "SELECT 1;\nSELECT 2 \nSELECT 3;";
|
||||
const doc = Text.of(sql.split("\n"));
|
||||
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
|
||||
expect(executableStatementRangeAtCursor(cache, contentEnd)?.sql).toBe("SELECT 2");
|
||||
expect(executableStatementRangeAtCursor(cache, contentEnd + 1)?.sql).toBe("SELECT 2");
|
||||
});
|
||||
|
||||
it("keeps a trailing-whitespace cursor on a non-final line of a multi-line statement in the frame cache", () => {
|
||||
const sql = "SELECT * FROM `profiles`;\nSELECT * FROM `users` AS uu \nWHERE id = 2\nSELECT * FROM `orders`;";
|
||||
const doc = Text.of(sql.split("\n"));
|
||||
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
|
||||
const markerEnd = sql.indexOf("uu") + 2;
|
||||
const lineEnd = sql.indexOf("\n", markerEnd);
|
||||
const expected = "SELECT * FROM `users` AS uu \nWHERE id = 2";
|
||||
|
||||
for (let pos = markerEnd; pos < lineEnd; pos += 1) {
|
||||
expect(executableStatementRangeAtCursor(cache, pos)?.sql).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not attach a semicolon after a blank line to the previous statement", () => {
|
||||
const sql = "SELECT 1\n\n;";
|
||||
const doc = Text.of(sql.split("\n"));
|
||||
|
||||
@@ -29,6 +29,23 @@ function candidateSummaries(candidates: Array<{ kind: string; sql: string }>): s
|
||||
return candidates.map((candidate) => `${candidate.kind}:${candidate.sql.trim()}`);
|
||||
}
|
||||
|
||||
function trailingSpacePositionsAfter(sql: string, marker: string): number[] {
|
||||
const markerEnd = sql.indexOf(marker) + marker.length;
|
||||
const lineEnd = sql.indexOf("\n", markerEnd);
|
||||
const end = lineEnd === -1 ? sql.length : lineEnd;
|
||||
const positions: number[] = [];
|
||||
for (let p = markerEnd; p < end; p += 1) {
|
||||
if (sql[p] === " " || sql[p] === "\t") positions.push(p);
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
// Reported repro: trailing spaces sit after `AS uu` (a NON-final line of the
|
||||
// middle statement) with `WHERE id = 2` continuing on the next line. The cursor
|
||||
// is inside the statement body, so the middle statement must be kept.
|
||||
const screenshotSqlNoSemi = "SELECT * FROM `profiles`\nWHERE active = 1\nSELECT * FROM `users` AS uu \nWHERE id = 2\nSELECT * FROM `orders`\nWHERE status = 'paid'";
|
||||
const screenshotSqlWithSemi = "SELECT * FROM `profiles`;\nSELECT * FROM `users` AS uu \nWHERE id = 2\nSELECT * FROM `orders`;";
|
||||
|
||||
const oraclePlSqlFixture = `DECLARE
|
||||
v_order_count NUMBER;
|
||||
BEGIN
|
||||
@@ -582,6 +599,70 @@ GET /_cat/indices`;
|
||||
expect(statementRangeAtCursor(sql, indexOf(sql, "tbB"))?.sql.trim()).toBe(expected);
|
||||
});
|
||||
|
||||
it("keeps a trailing-whitespace cursor on a multi-line WITH statement tail", () => {
|
||||
const sql = "WITH x AS (SELECT 1)\nSELECT 2 \nSELECT 3;";
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
|
||||
for (const pos of [contentEnd, contentEnd + 1, contentEnd + 2]) {
|
||||
expect(statementRangeAtCursor(sql, pos)?.sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps a trailing-whitespace cursor on a UNION operand tail", () => {
|
||||
const sql = "SELECT 1\nUNION\nSELECT 2 \nSELECT 3;";
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
|
||||
expect(statementRangeAtCursor(sql, contentEnd + 1)?.sql).toBe("SELECT 1\nUNION\nSELECT 2");
|
||||
expect(statementRangeAtCursor(sql, contentEnd + 2)?.sql).toBe("SELECT 1\nUNION\nSELECT 2");
|
||||
});
|
||||
|
||||
it("keeps a trailing-whitespace cursor on an EXPLAIN target tail", () => {
|
||||
const sql = "EXPLAIN\nSELECT 2 \nSELECT 3;";
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
|
||||
expect(statementRangeAtCursor(sql, contentEnd + 1)?.sql).toBe("EXPLAIN\nSELECT 2");
|
||||
});
|
||||
|
||||
it("does not swallow the following statement when a trailing-whitespace cursor sits on a semicolon-terminated multi-line statement tail", () => {
|
||||
const sql = "SELECT 0;\nWITH x AS (SELECT 1)\nSELECT 2 \nSELECT 3;";
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
|
||||
expect(statementRangeAtCursor(sql, contentEnd + 1)?.sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
expect(statementRangeAtCursor(sql, contentEnd + 2)?.sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
});
|
||||
|
||||
it("keeps a trailing-whitespace cursor on a non-final line of a multi-line middle statement (no semicolons before)", () => {
|
||||
// Reported repro: spaces after `AS uu`, `WHERE id = 2` follows on the next line.
|
||||
const expected = "SELECT * FROM `users` AS uu \nWHERE id = 2";
|
||||
const positions = trailingSpacePositionsAfter(screenshotSqlNoSemi, "uu");
|
||||
|
||||
expect(positions.length).toBeGreaterThan(0);
|
||||
for (const pos of positions) {
|
||||
expect(statementRangeAtCursor(screenshotSqlNoSemi, pos, "mysql")?.sql).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps a trailing-whitespace cursor on a non-final line of a multi-line middle statement (semicolons before)", () => {
|
||||
const expected = "SELECT * FROM `users` AS uu \nWHERE id = 2";
|
||||
const positions = trailingSpacePositionsAfter(screenshotSqlWithSemi, "uu");
|
||||
|
||||
expect(positions.length).toBeGreaterThan(0);
|
||||
for (const pos of positions) {
|
||||
expect(statementRangeAtCursor(screenshotSqlWithSemi, pos, "mysql")?.sql).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps a trailing-whitespace cursor on the last line of a multi-line middle statement", () => {
|
||||
const sql = "SELECT 1;\nSELECT * FROM `users` AS uu\nWHERE id = 2 \nSELECT * FROM `orders`;";
|
||||
const expected = "SELECT * FROM `users` AS uu\nWHERE id = 2";
|
||||
const positions = trailingSpacePositionsAfter(sql, "= 2");
|
||||
|
||||
expect(positions.length).toBeGreaterThan(0);
|
||||
for (const pos of positions) {
|
||||
expect(statementRangeAtCursor(sql, pos, "mysql")?.sql).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps newline set-operation operands with ALL modifiers together", () => {
|
||||
const sql = "select * from tbA\nunion all\nselect * from tbB\nSELECT * FROM logs;";
|
||||
const range = statementRangeAtCursor(sql, indexOf(sql, "tbA"));
|
||||
@@ -1175,6 +1256,51 @@ WHERE t2.product_name = '12345'
|
||||
expect(candidateSummaries(candidates)).toEqual(["cursor:SELECT 2", "all:SELECT 1\nSELECT 2"]);
|
||||
});
|
||||
|
||||
it("uses the multi-line cursor statement at a trailing-whitespace tail instead of falling back to the whole script", () => {
|
||||
const sql = "WITH x AS (SELECT 1)\nSELECT 2 \nSELECT 3;";
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
const candidates = buildExecutionCandidates(sql, contentEnd + 1, "mysql");
|
||||
|
||||
expect(candidateKinds(candidates)).toEqual(["cursor", "all"]);
|
||||
expect(candidates[0].sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
expect(executionCandidateForMode(candidates, "current")?.sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
});
|
||||
|
||||
it("uses the cursor statement at a semicolon-terminated multi-line tail instead of swallowing the following statement", () => {
|
||||
const sql = "SELECT 0;\nWITH x AS (SELECT 1)\nSELECT 2 \nSELECT 3;";
|
||||
const contentEnd = sql.indexOf("SELECT 2") + "SELECT 2".length;
|
||||
const candidates = buildExecutionCandidates(sql, contentEnd + 2, "mysql");
|
||||
|
||||
expect(candidates[0].kind).toBe("cursor");
|
||||
expect(candidates[0].sql).toBe("WITH x AS (SELECT 1)\nSELECT 2");
|
||||
});
|
||||
|
||||
it("submits only the middle statement for a trailing-whitespace cursor on its non-final line (no semicolons before)", () => {
|
||||
const expected = "SELECT * FROM `users` AS uu \nWHERE id = 2";
|
||||
const pos = trailingSpacePositionsAfter(screenshotSqlNoSemi, "uu")[0];
|
||||
const candidates = buildExecutionCandidates(screenshotSqlNoSemi, pos, "mysql");
|
||||
|
||||
expect(executionCandidateForMode(candidates, "current")?.sql).toBe(expected);
|
||||
expect(executionCandidateForMode(candidates, "all")?.sql).toBe(screenshotSqlNoSemi);
|
||||
});
|
||||
|
||||
it("submits only the middle statement for a trailing-whitespace cursor on its non-final line (semicolons before)", () => {
|
||||
const expected = "SELECT * FROM `users` AS uu \nWHERE id = 2";
|
||||
const pos = trailingSpacePositionsAfter(screenshotSqlWithSemi, "uu")[0];
|
||||
const candidates = buildExecutionCandidates(screenshotSqlWithSemi, pos, "mysql");
|
||||
|
||||
expect(executionCandidateForMode(candidates, "current")?.sql).toBe(expected);
|
||||
});
|
||||
|
||||
it("submits only the middle statement, not the merged script, for a trailing-whitespace cursor on its last line", () => {
|
||||
const sql = "SELECT 1;\nSELECT * FROM `users` AS uu\nWHERE id = 2 \nSELECT * FROM `orders`;";
|
||||
const expected = "SELECT * FROM `users` AS uu\nWHERE id = 2";
|
||||
const pos = trailingSpacePositionsAfter(sql, "= 2")[0];
|
||||
const candidates = buildExecutionCandidates(sql, pos, "mysql");
|
||||
|
||||
expect(executionCandidateForMode(candidates, "current")?.sql).toBe(expected);
|
||||
});
|
||||
|
||||
it("uses the final soft statement when the cursor is on a standalone trailing semicolon", () => {
|
||||
const sql = "SELECT * FROM `t_0001`\nSELECT * FROM `t_0001` LIMIT 1\n;";
|
||||
const candidates = buildExecutionCandidates(sql, sql.lastIndexOf(";"), "mysql");
|
||||
|
||||
Reference in new issue
Block a user