From de7f2fe6b6ffc2faf55e4e2235e0182099879255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Thu, 2 Jul 2026 11:18:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20=E4=BF=AE=E5=A4=8DSQL=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E6=89=A7=E8=A1=8C=E5=AF=BC=E8=87=B4=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=85=A8=E9=87=8FSQL=E7=9A=84=E9=97=AE=E9=A2=98=20(#2384)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将执行SQL语句的逻辑从直接请求改为通过事件发射 - 增强了代码的可扩展性和可维护性 --- .../src/components/editor/QueryEditor.vue | 2 +- .../executableStatementRangeCache.spec.ts | 9 +++++++ .../lib/__tests__/sqlStatementRanges.spec.ts | 25 +++++++++++++++++++ apps/desktop/src/lib/sqlStatementRanges.ts | 5 ++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 8680c00f0a..64fbd7836f 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -598,7 +598,7 @@ function executeSqlStatementFromGutter(currentView: EditorViewType, line: { from if (!statementRange) return false; event.preventDefault(); event.stopPropagation(); - requestExecuteFromView(currentView, line.from, { ignoreSelection: true }); + emit("execute", statementRange.sql); currentView.focus(); return true; } diff --git a/apps/desktop/src/lib/__tests__/executableStatementRangeCache.spec.ts b/apps/desktop/src/lib/__tests__/executableStatementRangeCache.spec.ts index 39a5ea1691..6eca9447ed 100644 --- a/apps/desktop/src/lib/__tests__/executableStatementRangeCache.spec.ts +++ b/apps/desktop/src/lib/__tests__/executableStatementRangeCache.spec.ts @@ -18,6 +18,15 @@ describe("executableStatementRangeCacheForDoc", () => { expect(executableStatementRangeStartingAt(second, 10)?.sql).toBe("SELECT 2"); }); + it("resolves the exact multi-line statement for a gutter run button", () => { + const doc = Text.of(["SELECT *", "FROM apis AS ap", "LIMIT 100;", "", "SELECT *", "FROM menus AS mn", "LIMIT 100;"]); + + const cache = executableStatementRangeCacheForDoc(null, doc, "mysql"); + const secondStatementLine = doc.line(5); + + expect(executableStatementRangeStartingAt(cache, secondStatementLine.from)?.sql).toBe("SELECT *\nFROM menus AS mn\nLIMIT 100"); + }); + it("rebuilds the cache when the document instance changes", () => { const firstDoc = Text.of(["SELECT 1;"]); const secondDoc = Text.of(["SELECT 1;"]); diff --git a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts index 6ea690a3d0..889a3b0d38 100644 --- a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts +++ b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts @@ -216,6 +216,31 @@ describe("statementRangeAtCursor", () => { expect(range?.sql.trim()).toBe("UPDATE users\nSET name = 'a'\nWHERE id = 1"); }); + it("keeps MySQL ALTER TABLE column comments with the column definition", () => { + const sql = + "ALTER TABLE `yb_course_order`\n ADD COLUMN `audit_status` tinyint(4) DEFAULT NULL\n COMMENT '审核状态:0-待审核,1-已通过,2-已拒绝',\n ADD COLUMN `close_reason` varchar(30) DEFAULT NULL\n COMMENT '关闭原因:timeout-超时关闭,cancel-取消关闭,refund-退款关闭',\n ADD COLUMN `paid_completion_time` datetime DEFAULT NULL\n COMMENT '订单完成支付(付清)时间 首次全额支付完成时记录,全部退款后不重置';"; + + expect(statementRangeAtCursor(sql, indexOf(sql, "ALTER"), "mysql")?.sql.trim()).toBe(sql.slice(0, -1)); + expect(statementRangeAtCursor(sql, indexOf(sql, "close_reason"), "mysql")?.sql.trim()).toBe(sql.slice(0, -1)); + expect(rangeSqlTexts(executableStatementRanges(sql, "mysql"))).toEqual([sql.slice(0, -1)]); + }); + + it("keeps MySQL ALTER TABLE drop column clauses with the statement", () => { + const sql = "ALTER TABLE t\n DROP COLUMN a,\n DROP COLUMN b;"; + + expect(statementRangeAtCursor(sql, indexOf(sql, "ALTER"), "mysql")?.sql.trim()).toBe(sql.slice(0, -1)); + expect(statementRangeAtCursor(sql, indexOf(sql, "DROP COLUMN b"), "mysql")?.sql.trim()).toBe(sql.slice(0, -1)); + expect(rangeSqlTexts(executableStatementRanges(sql, "mysql"))).toEqual([sql.slice(0, -1)]); + }); + + it("keeps MySQL ALTER TABLE alter column clauses with the statement", () => { + const sql = "ALTER TABLE t\n ALTER COLUMN name SET NOT NULL;"; + + expect(statementRangeAtCursor(sql, indexOf(sql, "ALTER"), "mysql")?.sql.trim()).toBe(sql.slice(0, -1)); + expect(statementRangeAtCursor(sql, indexOf(sql, "ALTER COLUMN"), "mysql")?.sql.trim()).toBe(sql.slice(0, -1)); + expect(rangeSqlTexts(executableStatementRanges(sql, "mysql"))).toEqual([sql.slice(0, -1)]); + }); + it("keeps insert-select with the INSERT statement", () => { const sql = "INSERT INTO archived_users (id, name)\nSELECT id, name FROM users\nUPDATE users SET archived = 1;"; const range = statementRangeAtCursor(sql, indexOf(sql, "archived_users")); diff --git a/apps/desktop/src/lib/sqlStatementRanges.ts b/apps/desktop/src/lib/sqlStatementRanges.ts index 29862b2eb9..a4a1787988 100644 --- a/apps/desktop/src/lib/sqlStatementRanges.ts +++ b/apps/desktop/src/lib/sqlStatementRanges.ts @@ -99,6 +99,7 @@ const WITH_MAIN_STATEMENT_KEYWORDS = new Set(["SELECT", "INSERT", "UPDATE", "DEL const EXPLAIN_STATEMENT_KEYWORDS = new Set(["SELECT", "WITH", "INSERT", "UPDATE", "DELETE", "MERGE", "CREATE", "ALTER", "DROP"]); const CREATE_BODY_KEYWORDS = new Set(["SELECT", "WITH", "BEGIN", "DECLARE"]); const INSERT_BODY_KEYWORDS = new Set(["SELECT", "WITH"]); +const ALTER_BODY_KEYWORDS = new Set(["ADD", "ALTER", "COMMENT", "DROP", "MODIFY", "RENAME", "SET"]); const ORACLE_LIKE_PL_SQL_DATABASES: ReadonlySet = new Set(["oracle", "dameng", "gaussdb", "yashandb", "oscar", "oceanbase-oracle"]); const ORACLE_PL_SQL_BLOCK_STARTERS = new Set(["DECLARE", "BEGIN"]); const ORACLE_PL_SQL_CREATE_OBJECT_TYPES = new Set(["FUNCTION", "PROCEDURE", "TRIGGER", "PACKAGE", "PACKAGE BODY", "TYPE", "TYPE BODY"]); @@ -444,6 +445,10 @@ function splitStatementRangeAtSoftStarts(sql: string, statement: RawStatement, d continue; } + if (currentBodyKeyword === "ALTER" && ALTER_BODY_KEYWORDS.has(lineStart.keyword)) { + continue; + } + boundaries.push(lineStart); currentKeyword = lineStart.keyword; currentExplainTargetKeyword = explainLikeTargetKeywordAt(sql, lineStart.from);