mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-02 02:34:42 +08:00
fix(mysql): execute adjacent statement on delimiter directive lines
This commit is contained in:
@@ -1409,10 +1409,23 @@ FROM orders;`;
|
||||
expect(range?.sql).toBe("SELECT 1");
|
||||
});
|
||||
|
||||
it("skips MySQL delimiter commands when resolving the cursor statement", () => {
|
||||
it("resolves MySQL delimiter command lines to the nearest executable statement", () => {
|
||||
const sql = "select COUNT(1) FROM your_table;\ndelimiter ;;\nselect COUNT(1) FROM your_table;\n\n;;\ndelimiter ;";
|
||||
expect(statementRangeAtCursor(sql, indexOf(sql, "COUNT", 2), "mysql")?.sql.trim()).toBe("select COUNT(1) FROM your_table;");
|
||||
expect(statementRangeAtCursor(sql, indexOf(sql, "delimiter"), "mysql")).toBeNull();
|
||||
// A caret on a leading `delimiter ;;` line targets the statement it introduces.
|
||||
expect(statementRangeAtCursor(sql, indexOf(sql, "delimiter"), "mysql")?.sql.trim()).toBe("select COUNT(1) FROM your_table;");
|
||||
// A trailing `delimiter ;` has nothing after it, so it targets the statement above.
|
||||
expect(statementRangeAtCursor(sql, indexOf(sql, "delimiter", 2), "mysql")?.sql.trim()).toBe("select COUNT(1) FROM your_table;");
|
||||
});
|
||||
|
||||
it("targets the surrounding MySQL statements for delimiter lines in a routine script", () => {
|
||||
const routine = mysqlDelimitedRoutineFixture.slice(mysqlDelimitedRoutineFixture.indexOf("CREATE PROCEDURE"), mysqlDelimitedRoutineFixture.indexOf(" //\nDELIMITER"));
|
||||
expect(statementRangeAtCursor(mysqlDelimitedRoutineFixture, indexOf(mysqlDelimitedRoutineFixture, "DELIMITER"), "mysql")?.sql.trim()).toBe(routine);
|
||||
expect(statementRangeAtCursor(mysqlDelimitedRoutineFixture, indexOf(mysqlDelimitedRoutineFixture, "DELIMITER", 2), "mysql")?.sql.trim()).toBe("CALL sp_insert_random_users(100)");
|
||||
});
|
||||
|
||||
it("returns null for a MySQL script made only of delimiter commands", () => {
|
||||
expect(statementRangeAtCursor("delimiter //\n", 0, "mysql")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns the full MySQL routine block for cursors inside nested statements", () => {
|
||||
@@ -1855,6 +1868,13 @@ WHERE t2.product_name = '12345'
|
||||
expect(candidateSummaries(candidates)).toEqual(["cursor:select COUNT(1) FROM your_table;", "all:select COUNT(1) FROM your_table;\ndelimiter ;;\nselect COUNT(1) FROM your_table;\n\n;;\ndelimiter ;"]);
|
||||
});
|
||||
|
||||
it("builds a cursor candidate for a MySQL delimiter command line (issue #9485)", () => {
|
||||
const sql = "select COUNT(1) FROM your_table;\ndelimiter ;;\nselect COUNT(1) FROM your_table;\n\n;;\ndelimiter ;";
|
||||
const candidates = buildExecutionCandidates(sql, indexOf(sql, "delimiter"), "mysql");
|
||||
expect(candidateKinds(candidates)).toEqual(["cursor", "all"]);
|
||||
expect(candidates[0].sql).toBe("select COUNT(1) FROM your_table;");
|
||||
});
|
||||
|
||||
it("uses the current SQL Server batch for cursor candidates", () => {
|
||||
const sql = "SELECT 1\nGO\nSELECT 2;";
|
||||
const candidates = buildExecutionCandidates(sql, indexOf(sql, "2"), "sqlserver");
|
||||
|
||||
@@ -709,6 +709,14 @@ export function statementRangeAtCursor(sql: string, cursorPos: number, databaseT
|
||||
}
|
||||
}
|
||||
|
||||
// A MySQL `delimiter X` line is a client directive, not executable SQL, so it
|
||||
// never forms a statement range of its own. When the caret rests on such a
|
||||
// line, target the statement the directive introduces (or the closest
|
||||
// preceding statement when the directive ends the script) instead of
|
||||
// reporting that there is nothing to run. See issue #9485.
|
||||
const directiveRange = mysqlDelimiterDirectiveCursorRange(sql, pos, databaseType, parameterOptions, statements);
|
||||
if (directiveRange) return directiveRange;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2192,6 +2200,29 @@ function parseDelimiterCommand(line: string): string | null {
|
||||
return delimiter ? delimiter : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a caret resting on a MySQL `delimiter X` client-directive line to
|
||||
* the nearest executable statement: the statement the directive introduces, or
|
||||
* the statement just before it when the directive has nothing after it.
|
||||
*/
|
||||
function mysqlDelimiterDirectiveCursorRange(sql: string, pos: number, databaseType: DatabaseType | undefined, parameterOptions: SqlParameterOptions | undefined, statements: RawStatement[]): SqlTextRange | null {
|
||||
if (databaseType !== "mysql" || statements.length === 0) return null;
|
||||
const lineStart = sql.lastIndexOf("\n", pos - 1) + 1;
|
||||
const lineEnd = findLineEnd(sql, pos);
|
||||
let directiveStart = lineStart;
|
||||
while (directiveStart < lineEnd && (sql[directiveStart] === " " || sql[directiveStart] === "\t")) directiveStart += 1;
|
||||
if (!startsDelimiterCommand(sql, directiveStart)) return null;
|
||||
if (parseDelimiterCommand(sql.slice(directiveStart, lineEnd)) === null) return null;
|
||||
|
||||
const following = statements.find((statement) => statement.from >= lineEnd);
|
||||
if (following) {
|
||||
return rangeFor(splitStatementRangeAtSoftStarts(sql, following, databaseType, parameterOptions)[0] ?? following, sql);
|
||||
}
|
||||
const preceding = statements[statements.length - 1];
|
||||
const precedingSoftRanges = splitStatementRangeAtSoftStarts(sql, preceding, databaseType, parameterOptions);
|
||||
return rangeFor(precedingSoftRanges[precedingSoftRanges.length - 1] ?? preceding, sql);
|
||||
}
|
||||
|
||||
function findLineEnd(sql: string, pos: number): number {
|
||||
const newline = sql.indexOf("\n", pos);
|
||||
const carriageReturn = sql.indexOf("\r", pos);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
Reference in New Issue
Block a user