diff --git a/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts index 09a48ecb8b..7e37e186f6 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts @@ -372,6 +372,13 @@ COMMENT = '测试';`; expect(rangeSqlTexts(executableStatementRanges(sql))).toEqual(["CREATE TABLE users (id int)", "COMMENT ON TABLE users IS 'Users'"]); }); + it("keeps a line-start comment column inside a select projection", () => { + const sql = "SELECT\n id,\n comment,\n created_at\nFROM project_info\nWHERE deleted = 0;"; + + expect(statementRangeAtCursor(sql, indexOf(sql, "comment"))?.sql.trim()).toBe(sql.slice(0, -1)); + expect(rangeSqlTexts(executableStatementRanges(sql))).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;"; diff --git a/apps/desktop/src/lib/sql/sqlStatementRanges.ts b/apps/desktop/src/lib/sql/sqlStatementRanges.ts index 3caa1375c4..75aa3a9a19 100644 --- a/apps/desktop/src/lib/sql/sqlStatementRanges.ts +++ b/apps/desktop/src/lib/sql/sqlStatementRanges.ts @@ -718,6 +718,9 @@ function softStatementKeywordAt(sql: string, pos: number, databaseType?: Databas if (!match) return null; const keyword = match[0].toUpperCase(); if (keyword === "REPLACE" && nextNonWhitespaceChar(sql, pos + match[0].length) === "(") return null; + // COMMENT is also a common column name. Only COMMENT ON starts a standalone + // SQL command; otherwise a line-start projection column must stay in SELECT. + if (keyword === "COMMENT" && nextSqlWord(sql, pos + match[0].length) !== "ON") return null; return softStatementStartKeywords(databaseType).has(keyword) ? keyword : null; } @@ -913,6 +916,12 @@ function nextNonWhitespaceChar(sql: string, pos: number): string | null { return i < sql.length ? sql[i] : null; } +function nextSqlWord(sql: string, pos: number): string | null { + let i = pos; + while (i < sql.length && isSqlWhitespace(sql[i])) i += 1; + return /^[A-Za-z_][\w$]*/.exec(sql.slice(i))?.[0]?.toUpperCase() ?? null; +} + function isExplainLikeKeyword(keyword: string | null): boolean { return keyword === "EXPLAIN" || keyword === "DESCRIBE" || keyword === "DESC"; }