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}