From 4bac6b56de1402d756f8ff9f785231bd8e893619 Mon Sep 17 00:00:00 2001 From: Thanadon Kongkanun <71448810+thanadon-dev@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:42:32 +0700 Subject: [PATCH] fix(sql): treat GET DIAGNOSTICS targets as MySQL user variables Refs: #8927 --- .../lib/__tests__/sql/sqlParameters.spec.ts | 35 +++++++++++++++++++ apps/desktop/src/lib/sql/sqlParameters.ts | 17 +++++++++ 2 files changed, 52 insertions(+) diff --git a/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts index 634e422553..914eee67cc 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts @@ -292,6 +292,41 @@ describe("extractSqlParameters", () => { expect(extractSqlParameters(sql, { databaseType: "mysql" })).toEqual(["input_id", "tenant_id"]); }); + it("ignores MySQL user variables assigned by GET DIAGNOSTICS", () => { + const sql = ` + get diagnostics condition 1 @err_state = returned_sqlstate, @err_msg = message_text; + select concat('failed: ', @err_state, ' ', @err_msg) as result; + `; + + expect(extractSqlParameters(sql, { databaseType: "mysql" })).toEqual([]); + }); + + it("ignores GET CURRENT/STACKED DIAGNOSTICS targets and the statement-level row count", () => { + const sql = ` + get diagnostics @affected = row_count; + get current diagnostics condition 1 @current_state = returned_sqlstate; + get stacked diagnostics condition 1 @stacked_state = returned_sqlstate; + select @affected, @current_state, @stacked_state; + `; + + expect(extractSqlParameters(sql, { databaseType: "mysql" })).toEqual([]); + }); + + it("keeps ordinary MySQL template parameters next to GET DIAGNOSTICS targets", () => { + const sql = ` + get diagnostics condition 1 @err_msg = message_text; + select * from audit_log where tenant_id = @tenant_id and note = @err_msg; + `; + + expect(extractSqlParameters(sql, { databaseType: "mysql" })).toEqual(["tenant_id"]); + }); + + it("keeps a template parameter on a column named get", () => { + const sql = "select get from api_methods where tenant_id = @tenant_id"; + + expect(extractSqlParameters(sql, { databaseType: "mysql" })).toEqual(["tenant_id"]); + }); + it("ignores SQL Server procedure parameters declared in routine definitions", () => { const sql = ` create procedure dbo.search_orders diff --git a/apps/desktop/src/lib/sql/sqlParameters.ts b/apps/desktop/src/lib/sql/sqlParameters.ts index db4d85818c..bc85194a79 100644 --- a/apps/desktop/src/lib/sql/sqlParameters.ts +++ b/apps/desktop/src/lib/sql/sqlParameters.ts @@ -1278,6 +1278,13 @@ function collectNativeSqlServerParameters(sql: string, databaseType?: DatabaseTy i = collectSelectAssignmentVariables(sql, i + "select".length, declared, databaseType); continue; } + if (matchesWord(sql, i, "get")) { + const diagnosticsEnd = readGetDiagnosticsEnd(sql, i); + if (diagnosticsEnd !== null) { + i = collectSetStatementVariables(sql, diagnosticsEnd, declared, databaseType); + continue; + } + } if ((matchesWord(sql, i, "create") || matchesWord(sql, i, "alter")) && isRoutineDefinitionStart(sql, i)) { i = collectRoutineDefinitionVariables(sql, i, declared, databaseType); continue; @@ -1526,6 +1533,16 @@ function collectExecNamedArgumentStarts(sql: string, start: number, ignoredStart return i; } +// MySQL's GET DIAGNOSTICS writes its results into the user variables on the left +// of each assignment (`GET DIAGNOSTICS CONDITION 1 @err = MESSAGE_TEXT`), exactly +// as SET and SELECT ... INTO do. Returns the offset just past the statement's +// leading keywords, or null when `start` is an ordinary `get` identifier. +function readGetDiagnosticsEnd(sql: string, start: number): number | null { + let keyword = readNextKeyword(sql, start + "get".length); + if (keyword?.word === "current" || keyword?.word === "stacked") keyword = readNextKeyword(sql, keyword.end); + return keyword?.word === "diagnostics" ? keyword.end : null; +} + function isRoutineDefinitionStart(sql: string, start: number): boolean { const keyword = matchesWord(sql, start, "create") ? "create" : matchesWord(sql, start, "alter") ? "alter" : ""; if (!keyword) return false;