From 445609e1967f215934dfa637e01f1f8fa10c05e3 Mon Sep 17 00:00:00 2001 From: Yann Amsellem Date: Wed, 23 Apr 2025 18:53:57 +0200 Subject: [PATCH 1/3] feat: add cancelable requests for remote --- src/lib/olap-engine/engine-chdb.ts | 8 +++---- src/lib/olap-engine/engine-remote.ts | 10 ++++----- src/lib/olap-engine/index.ts | 4 +++- src/routes/+page.svelte | 33 ++++++++++++++++++++-------- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/lib/olap-engine/engine-chdb.ts b/src/lib/olap-engine/engine-chdb.ts index 532260f..4c3fbb5 100644 --- a/src/lib/olap-engine/engine-chdb.ts +++ b/src/lib/olap-engine/engine-chdb.ts @@ -1,6 +1,6 @@ import { invoke } from '@tauri-apps/api/core'; import { InternalEventEmitter } from './EventListener'; -import type { Events, OLAPEngine, OLAPResponse, Table } from './index'; +import type { Events, ExecOptions, OLAPEngine, OLAPResponse, Table } from './index'; import CLICKHOUSE_GET_SCHEMA from './queries/clickhouse_get_schema.sql?raw'; import CLICKHOUSE_GET_UDFS from './queries/clickhouse_get_udfs.sql?raw'; @@ -11,7 +11,7 @@ export class CHDBEngine extends InternalEventEmitter implements OLAPEngi await this.exec(CLICKHOUSE_INIT_DB); } - async exec(query: string, _emit = true) { + async exec(query: string, options: ExecOptions = {}, _emit = true) { try { const r: string = await invoke('query', { query }); @@ -29,13 +29,13 @@ export class CHDBEngine extends InternalEventEmitter implements OLAPEngi } async getSchema() { - const response = await this.exec(CLICKHOUSE_GET_SCHEMA, false); + const response = await this.exec(CLICKHOUSE_GET_SCHEMA, {}, false); if (!response) return []; return response.data as Table[]; } async getUDFs() { - const response = await this.exec(CLICKHOUSE_GET_UDFS, false); + const response = await this.exec(CLICKHOUSE_GET_UDFS, {}, false); if (!response) return []; return response.data.map((row) => row.name as string); diff --git a/src/lib/olap-engine/engine-remote.ts b/src/lib/olap-engine/engine-remote.ts index 3da2d5b..f29efc3 100644 --- a/src/lib/olap-engine/engine-remote.ts +++ b/src/lib/olap-engine/engine-remote.ts @@ -1,5 +1,5 @@ import { InternalEventEmitter } from './EventListener'; -import type { Events, OLAPEngine, OLAPResponse, Table } from './index'; +import type { Events, ExecOptions, OLAPEngine, OLAPResponse, Table } from './index'; import CLICKHOUSE_GET_SCHEMA from './queries/clickhouse_get_schema.sql?raw'; import CLICKHOUSE_GET_UDFS from './queries/clickhouse_get_udfs.sql?raw'; @@ -7,10 +7,10 @@ import CLICKHOUSE_GET_UDFS from './queries/clickhouse_get_udfs.sql?raw'; export class RemoteEngine extends InternalEventEmitter implements OLAPEngine { async init() {} - async exec(query: string, _emit = true) { + async exec(query: string, options: ExecOptions = {}, _emit = true) { try { const proxy = new URLSearchParams(window.location.search).get('proxy') ?? CLICKHOUSE_URL; - const response = await fetch(proxy, { method: 'POST', body: query }); + const response = await fetch(proxy, { method: 'POST', body: query, signal: options.signal }); const r = await response.text(); if (!r) throw new Error(`Empty Response`); @@ -29,13 +29,13 @@ export class RemoteEngine extends InternalEventEmitter implements OLAPEn } async getSchema() { - const response = await this.exec(CLICKHOUSE_GET_SCHEMA, false); + const response = await this.exec(CLICKHOUSE_GET_SCHEMA, {}, false); if (!response) return []; return response.data as Table[]; } async getUDFs() { - const response = await this.exec(CLICKHOUSE_GET_UDFS, false); + const response = await this.exec(CLICKHOUSE_GET_UDFS, {}, false); if (!response) return []; return response.data.map((row) => row.name as string); diff --git a/src/lib/olap-engine/index.ts b/src/lib/olap-engine/index.ts index de95581..21954b3 100644 --- a/src/lib/olap-engine/index.ts +++ b/src/lib/olap-engine/index.ts @@ -28,9 +28,11 @@ export interface Table { export type Events = 'error' | 'success'; +export type ExecOptions = { signal?: AbortSignal | null }; + export interface OLAPEngine extends IListener { init(): Promise; - exec(query: string): Promise; + exec(query: string, options?: ExecOptions): Promise; getSchema(): Promise; getUDFs(): Promise; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 775f27d..4d13260 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -16,6 +16,7 @@ import { FileDropEventManager } from '$lib/FileDropEventManager'; import Bars3 from '$lib/icons/Bars3.svelte'; import Bolt from '$lib/icons/Bolt.svelte'; + import CircleStopSolid from '$lib/icons/CircleStopSolid.svelte'; import Copy from '$lib/icons/Copy.svelte'; import MagicWand from '$lib/icons/MagicWand.svelte'; import PanelBottom from '$lib/icons/PanelBottom.svelte'; @@ -55,6 +56,7 @@ let response = $state.raw(); let loading = $state(false); let counter = $state>(); + let abortController: AbortController | undefined; const cache = new IndexedDBCache({ dbName: 'query-cache', storeName: 'response-data' }); let cached = $state(false); @@ -79,11 +81,13 @@ } cached = false; - response = await engine.exec(query); + abortController = new AbortController(); + response = await engine.exec(query, { signal: abortController.signal }); await cache.set(query, response); } finally { loading = false; counter?.stop(); + abortController = undefined; } } @@ -291,6 +295,7 @@ let errors = $state.raw([]); engine.on('error', (e) => { if (e instanceof Error) { + if (e.message === 'Canceled') return; errors = errors.concat({ level: 'error', timestamp: new Date(), data: e.message }); bottomPanel.open = true; bottomPanelTab = 'logs'; @@ -479,14 +484,24 @@ LIMIT 100;`; > - + {#if loading} + + {:else} + + {/if} - {#if loading} + {#if loading && engine.isAbortable} {:else}