fix(mysql): preserve nested routine blocks

This commit is contained in:
zipg authored and GitHub committed 2026-08-13 20:35:16 +08:00
1 parent d64908c596
commit 65cd4cfe28
5 files changed
+179 -19

No files matched your search

@@ -37,6 +37,24 @@ describe("executableStatementRangeCacheForDoc", () => {
expect(executableStatementRangeStartingAt(cache, secondStatementLine.from)?.sql).toBe("SELECT *\nFROM menus AS mn\nLIMIT 100");
});
it("only exposes the routine start as executable when a MySQL procedure contains a CASE expression", () => {
const sql = [
"CREATE PROCEDURE p_case()",
"BEGIN",
" INSERT INTO audit_log (status_text)",
" SELECT CASE WHEN active = 1 THEN 'active' ELSE 'inactive' END;",
" CASE WHEN active = 1 THEN SET @status_code = 1; ELSE SET @status_code = 0; END CASE;",
" DELETE FROM stale_rows WHERE expires_at < NOW();",
"END;",
].join("\n");
const doc = Text.of(sql.split("\n"));
const cache = executableStatementRangeCacheForDoc(null, doc, "mysql");
expect(executableStatementRangeStartingAt(cache, doc.line(1).from)?.sql).toBe(sql.slice(0, -1));
expect(executableStatementRangeStartingAt(cache, doc.line(6).from)).toBeNull();
expect(executableStatementRangeAtCursor(cache, doc.line(6).from + 4)?.sql).toBe(sql.slice(0, -1));
});
it("keeps MyBatis parameters in a Kingbase gutter execution range", () => {
const sql = ["SELECT sum(nvl(a.medfee_sumamt, 0)) AS medfee_sumamt, a.insutype", "FROM yd_org_decla_detail a", "WHERE a.busin_type = '1' AND a.clr_ym = #{ym}", "GROUP BY a.clr_ym, a.insutype;"].join("\n");
const doc = Text.of(sql.split("\n"));
@@ -230,6 +230,39 @@ BEGIN
END;
SELECT 2;`;
const mysqlRoutineWithCaseExpressionFixture = `CREATE PROCEDURE p_case()
BEGIN
INSERT INTO audit_log (status_text)
SELECT CASE
WHEN active = 1 THEN 'active'
ELSE 'inactive'
END;
CASE
WHEN active = 1 THEN SET @status_code = 1;
ELSE SET @status_code = 0;
END CASE;
DELETE FROM stale_rows
WHERE expires_at < NOW();
END;
SELECT 2;`;
const mysqlRoutineWithNestedCaseBlockFixture = `CREATE PROCEDURE p_nested_case()
BEGIN
CASE
WHEN active = 1 THEN
BEGIN
SET @status_code = 1;
END;
ELSE SET @status_code = 0;
END CASE;
DELETE FROM stale_rows
WHERE expires_at < NOW();
END;
SELECT 2;`;
const mysqlDelimitedRoutineFixture = `DELIMITER //
CREATE PROCEDURE sp_insert_random_users(IN p_count INT)
BEGIN
@@ -342,6 +375,10 @@ describe("splitSqlStatementRanges", () => {
expect(ranges[0].sql).not.toMatch(/END;$/);
});
it("closes nested MySQL CASE and BEGIN blocks in their opening order", () => {
expect(rangeSqlTexts(splitSqlStatementRanges(mysqlRoutineWithNestedCaseBlockFixture, "mysql"))).toEqual([mysqlRoutineWithNestedCaseBlockFixture.slice(0, mysqlRoutineWithNestedCaseBlockFixture.indexOf("\nSELECT 2;")).replace(/;$/, "").trim(), "SELECT 2"]);
});
it("does not merge regular MySQL transaction statements as routine blocks", () => {
const sql = "BEGIN; INSERT INTO t VALUES (1); COMMIT;";
expect(rangeSqlTexts(splitSqlStatementRanges(sql, "mysql"))).toEqual(["BEGIN", "INSERT INTO t VALUES (1)", "COMMIT"]);
@@ -1105,6 +1142,14 @@ describe("executableStatementRanges", () => {
expect(rangeSqlTexts(executableStatementRanges(mysqlRoutineWithLoopsFixture, "mysql"))).toEqual([mysqlRoutineWithLoopsFixture.slice(0, mysqlRoutineWithLoopsFixture.indexOf("\nSELECT 2;")).replace(/;$/, "").trim(), "SELECT 2"]);
});
it("does not treat a CASE expression ending as the end of a MySQL routine", () => {
expect(rangeSqlTexts(executableStatementRanges(mysqlRoutineWithCaseExpressionFixture, "mysql"))).toEqual([mysqlRoutineWithCaseExpressionFixture.slice(0, mysqlRoutineWithCaseExpressionFixture.indexOf("\nSELECT 2;")).replace(/;$/, "").trim(), "SELECT 2"]);
});
it("does not split MySQL routine ranges when a CASE branch contains a BEGIN block", () => {
expect(rangeSqlTexts(executableStatementRanges(mysqlRoutineWithNestedCaseBlockFixture, "mysql"))).toEqual([mysqlRoutineWithNestedCaseBlockFixture.slice(0, mysqlRoutineWithNestedCaseBlockFixture.indexOf("\nSELECT 2;")).replace(/;$/, "").trim(), "SELECT 2"]);
});
it("does not expose run targets for statements inside a delimited MySQL routine", () => {
expect(rangeSqlTexts(executableStatementRanges(mysqlDelimitedRoutineFixture, "mysql"))).toEqual([mysqlDelimitedRoutineFixture.slice(mysqlDelimitedRoutineFixture.indexOf("CREATE PROCEDURE"), mysqlDelimitedRoutineFixture.indexOf(" //\nDELIMITER")), "CALL sp_insert_random_users(100)"]);
});