diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9815718c2b..b7ec6f3aac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,8 @@ jobs: runs-on: ubuntu-24.04 env: NPM_CONFIG_LOGLEVEL: error + # Cold vue-tsc runs exceed 4 GiB; public Linux runners provide 16 GiB RAM. + NODE_OPTIONS: --max-old-space-size=8192 steps: - uses: actions/checkout@v7 diff --git a/apps/desktop/src/components/editor/__tests__/QueryEditor.componentSplit.integration.spec.ts b/apps/desktop/src/components/editor/__tests__/QueryEditor.componentSplit.integration.spec.ts index 0af33d9111..84974f9503 100644 --- a/apps/desktop/src/components/editor/__tests__/QueryEditor.componentSplit.integration.spec.ts +++ b/apps/desktop/src/components/editor/__tests__/QueryEditor.componentSplit.integration.spec.ts @@ -79,6 +79,20 @@ function keydown(target: HTMLElement, key: string) { } describe("QueryEditor component split integration", () => { + it("executes each IRIS query separately after the Contains operator", async () => { + const first = "select * from oec_order_adminstatus where STAT_Code [ '123'"; + const second = "select top 10 * from Ens_HOSDocument"; + const source = first + "\n\n" + second; + const { editor, view, onExecute } = await mountEditor({ modelValue: source, databaseType: "iris", dialect: "postgres" }); + view.dispatch({ selection: { anchor: source.indexOf(second) + 7 } }); + expect(editor.requestExecute({ bypassPicker: true })).toBe(true); + expect(onExecute.mock.lastCall?.[0].selectedSql).toBe(second); + + view.dispatch({ selection: { anchor: 7 } }); + expect(editor.requestExecute({ bypassPicker: true })).toBe(true); + expect(onExecute.mock.lastCall?.[0].selectedSql).toBe(first); + }); + it("preserves upstream structure-peek focus and insertion context across updates and unmount", async () => { const { view, props, unmount } = await mountEditor({ connectionId: "peek-a", database: "demo", schema: "public" }); expect(queryEditorInsertContext(view)).toEqual({ connectionId: "peek-a", database: "demo", schema: "public", databaseType: "mysql" }); diff --git a/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts index 37ddcf48f6..18092b7bdf 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlStatementRanges.spec.ts @@ -1684,6 +1684,20 @@ SET t.no = s.no`; }); describe("executableStatementRanges", () => { + it.each(["\n\n", ";\n", "; "])("keeps SELECT after an IRIS Contains operator separate with separator %j", (separator) => { + const first = "select * from oec_order_adminstatus where STAT_Code [ '123'"; + const second = "select top 10 * from Ens_HOSDocument"; + const sql = first + separator + second; + + expect(rangeSqlTexts(executableStatementRanges(sql, "iris"))).toEqual([first, second]); + expect(currentExecutableStatementRange(sql, sql.indexOf(second) + 7, "iris")?.sql).toBe(second); + }); + + it("keeps SQL Server bracket identifiers containing semicolons quoted", () => { + const sql = "SELECT [name;part] FROM [table;part]; SELECT 2;"; + expect(rangeSqlTexts(executableStatementRanges(sql, "sqlserver"))).toEqual(["SELECT [name;part] FROM [table;part]", "SELECT 2"]); + }); + it.each(["doris", "starrocks"] as const)("keeps %s half-open range partitions from swallowing the next statement", (databaseType) => { const first = `CREATE TABLE fixed_partitions_1 ( sale_date date NULL, diff --git a/apps/desktop/src/lib/sql/sqlStatementRanges.ts b/apps/desktop/src/lib/sql/sqlStatementRanges.ts index 6afe36f3fc..73d2b6a331 100644 --- a/apps/desktop/src/lib/sql/sqlStatementRanges.ts +++ b/apps/desktop/src/lib/sql/sqlStatementRanges.ts @@ -214,7 +214,8 @@ function splitElasticsearchRestRequestRanges(sql: string): RawStatement[] | unde type QuoteState = "none" | "single" | "double" | "backtick" | "bracket" | "dollar"; function usesBracketIdentifierQuotes(databaseType?: DatabaseType): boolean { - return databaseType !== "doris" && databaseType !== "starrocks"; + // IRIS uses `[` as its Contains operator, with no matching `]`. + return databaseType !== "doris" && databaseType !== "starrocks" && databaseType !== "iris"; } const COMMON_SOFT_STATEMENT_START_KEYWORDS = [ diff --git a/docs/screenshots/issue-10706-after.jpg b/docs/screenshots/issue-10706-after.jpg new file mode 100644 index 0000000000..19d24b5be6 Binary files /dev/null and b/docs/screenshots/issue-10706-after.jpg differ diff --git a/docs/screenshots/issue-10706-before.jpg b/docs/screenshots/issue-10706-before.jpg new file mode 100644 index 0000000000..3ddacc5e94 Binary files /dev/null and b/docs/screenshots/issue-10706-before.jpg differ