diff --git a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts index cac6d6e3f..7c0e404f1 100644 --- a/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts +++ b/apps/desktop/src/lib/__tests__/sqlStatementRanges.spec.ts @@ -108,10 +108,24 @@ describe("statementRangeAtCursor", () => { expect(range?.sql.trim()).toBe("SELECT 2"); }); - it("returns the next same-line statement when the cursor is in whitespace before it", () => { + it("returns the previous statement when the cursor is in same-line whitespace after its semicolon", () => { const sql = "SELECT 1; SELECT 2;"; const gapPos = sql.indexOf(";") + 2; const range = statementRangeAtCursor(sql, gapPos); + expect(range?.sql.trim()).toBe("SELECT 1"); + }); + + it("returns the previous statement when the cursor is just after its semicolon before a later statement", () => { + const sql = "SELECT *\nFROM system_dept;\n\nSELECT *\nFROM sys;"; + const gapPos = sql.indexOf(";") + 1; + const range = statementRangeAtCursor(sql, gapPos); + expect(range?.sql.trim()).toBe("SELECT *\nFROM system_dept"); + }); + + it("returns the next same-line statement when the cursor is inside it", () => { + const sql = "SELECT 1; SELECT 2;"; + const pos = indexOf(sql, "SELECT 2") + 1; + const range = statementRangeAtCursor(sql, pos); expect(range?.sql.trim()).toBe("SELECT 2"); }); diff --git a/apps/desktop/src/lib/sqlStatementRanges.ts b/apps/desktop/src/lib/sqlStatementRanges.ts index a886ba2a2..a4c69afeb 100644 --- a/apps/desktop/src/lib/sqlStatementRanges.ts +++ b/apps/desktop/src/lib/sqlStatementRanges.ts @@ -308,6 +308,11 @@ export function statementRangeAtCursor(sql: string, cursorPos: number, databaseT // the statement should still target that statement, while the returned // execution range remains tight around the SQL text itself. if (pos >= statement.hitFrom && pos < statement.from && sql.slice(pos, statement.from).trim() === "") { + const previous = statements[index - 1]; + if (previous && isCursorInSameLineDelimiterGap(sql, previous.to, pos)) { + const previousSoftRanges = splitStatementRangeAtSoftStarts(sql, previous, databaseType); + return rangeForCursorInSoftRanges(sql, previousSoftRanges, pos) ?? rangeFor(previous, sql); + } return rangeForCursorInSoftRanges(sql, softRanges, pos) ?? rangeFor(statement, sql); } @@ -320,6 +325,15 @@ export function statementRangeAtCursor(sql: string, cursorPos: number, databaseT return null; } +function isCursorInSameLineDelimiterGap(sql: string, previousStatementEnd: number, cursorPos: number): boolean { + if (cursorPos <= previousStatementEnd) return false; + const between = sql.slice(previousStatementEnd, cursorPos); + const delimiterIndex = between.lastIndexOf(";"); + if (delimiterIndex === -1) return false; + const afterDelimiter = between.slice(delimiterIndex + 1); + return !afterDelimiter.includes("\n") && between.slice(0, delimiterIndex).trim() === "" && afterDelimiter.trim() === ""; +} + function rangeForCursorInSoftRanges(sql: string, ranges: RawStatement[], pos: number): SqlTextRange | null { for (let index = 0; index < ranges.length; index += 1) { const range = ranges[index]; diff --git a/crates/dbx-core/src/query_result_sql.rs b/crates/dbx-core/src/query_result_sql.rs index 9abd63436..477bcf8f7 100644 --- a/crates/dbx-core/src/query_result_sql.rs +++ b/crates/dbx-core/src/query_result_sql.rs @@ -986,6 +986,7 @@ WHERE u.id = picked.id; database_type: Some(DatabaseType::Postgres), pagination: QueryPagination { limit: 100, offset: 0, session_id: None }, use_agent_cursor: false, + first_page_uses_actual_sql: false, }); assert_eq!(plan.sql_to_execute, sql);