feat(query-editor): 增强 SQL 编辑器执行与历史体验

- 支持仅执行选中 SQL、光标所在语句和增量新增语句

- 持久化查询草稿,避免重启后丢失历史 SQL

- 在表字段提示中展示注释信息

- 修复清空默认 SQL 后被自动回填的问题

Refs #483
This commit is contained in:
Syngnat
2026-05-23 17:07:47 +08:00
parent 09af56b1c2
commit b9c743d67e
20 changed files with 1431 additions and 119 deletions

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { findSqlStatementRanges, resolveCurrentSqlStatementRange } from './sqlStatementSelection';
import { findSqlStatementRanges, resolveCurrentSqlStatementRange, resolveExecutableSql } from './sqlStatementSelection';
describe('sqlStatementSelection', () => {
it('resolves the statement containing the cursor', () => {
@@ -38,4 +38,46 @@ describe('sqlStatementSelection', () => {
it('returns null when there is no statement', () => {
expect(resolveCurrentSqlStatementRange(' \n\t ', 0)).toBeNull();
});
it('prefers a non-empty selection for executable SQL', () => {
const sql = 'select 1;\nselect 2;';
expect(resolveExecutableSql(sql, sql.indexOf('1'), ' select selected ')?.sql).toBe(' select selected ');
expect(resolveExecutableSql(sql, sql.indexOf('1'), ' select selected ')?.source).toBe('selection');
});
it('uses the statement containing the cursor for executable SQL', () => {
const sql = 'select 1;\n\nselect 2 from users;\nselect 3';
expect(resolveExecutableSql(sql, sql.indexOf('users'))).toEqual({
sql: 'select 2 from users',
source: 'statement',
});
});
it('keeps execution on the statement when the cursor lands after its semicolon', () => {
const sql = 'select 1 as a;\nselect 2 as b;\n\nselect 3 as c;';
const afterSecondSemicolon = sql.indexOf('select 2 as b') + 'select 2 as b;'.length;
expect(resolveExecutableSql(sql, afterSecondSemicolon)).toEqual({
sql: 'select 2 as b',
source: 'statement',
});
});
it('falls back to the current line when the cursor is not inside a statement', () => {
const sql = 'select 1;\n\n select 2';
expect(resolveExecutableSql(sql, sql.indexOf('\n\n') + 1)).toBeNull();
expect(resolveExecutableSql(sql, sql.indexOf(' select 2'))).toEqual({
sql: 'select 2',
source: 'statement',
});
});
it('does not jump to the next statement when executing from blank space', () => {
const sql = 'select 1;\n\nselect 2;';
expect(resolveExecutableSql(sql, sql.indexOf('\n\n') + 1)).toBeNull();
});
});