diff --git a/src/lib/icons/Stop.svelte b/src/lib/icons/Stop.svelte new file mode 100644 index 0000000..4c3a835 --- /dev/null +++ b/src/lib/icons/Stop.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/src/lib/olap-engine/engine-chdb.ts b/src/lib/olap-engine/engine-chdb.ts index 532260f..eaab396 100644 --- a/src/lib/olap-engine/engine-chdb.ts +++ b/src/lib/olap-engine/engine-chdb.ts @@ -1,17 +1,19 @@ 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'; import CLICKHOUSE_INIT_DB from './queries/clickhouse_init_db.sql?raw'; export class CHDBEngine extends InternalEventEmitter implements OLAPEngine { + readonly isAbortable = false; + async init() { 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 +31,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..472ec4e 100644 --- a/src/lib/olap-engine/engine-remote.ts +++ b/src/lib/olap-engine/engine-remote.ts @@ -1,16 +1,18 @@ 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'; export class RemoteEngine extends InternalEventEmitter implements OLAPEngine { + readonly isAbortable = true; + 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 +31,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..1650e9b 100644 --- a/src/lib/olap-engine/index.ts +++ b/src/lib/olap-engine/index.ts @@ -28,9 +28,12 @@ export interface Table { export type Events = 'error' | 'success'; +export type ExecOptions = { signal?: AbortSignal | null }; + export interface OLAPEngine extends IListener { + readonly isAbortable: boolean; 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..a9316d9 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -25,6 +25,7 @@ import Plus from '$lib/icons/Plus.svelte'; import Save from '$lib/icons/Save.svelte'; import Sparkles from '$lib/icons/Sparkles.svelte'; + import Stop from '$lib/icons/Stop.svelte'; import type { Table } from '$lib/olap-engine'; import { engine, type OLAPResponse } from '$lib/olap-engine'; import { PanelState } from '$lib/PanelState.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 && engine.isAbortable} + + {:else} + + {/if}