Tony Powell commited on
Commit
8aa5ded
·
1 Parent(s): 65af11b

Add query input and persist it to / load from url

Browse files
Files changed (2) hide show
  1. src/App.tsx +50 -5
  2. src/useParquetTable.ts +80 -6
src/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useCallback, useState } from "react";
2
  import { Table } from "./table";
3
  import { Button } from "~/components/ui/button";
4
  import { Textarea } from "~/components/ui/textarea";
@@ -24,16 +24,32 @@ const usePersistedTextfield = (fieldName: string) => {
24
 
25
  function App() {
26
  const [datasetUrl, setDatasetUrl] = usePersistedTextfield("datasetUrl");
 
27
  const [nextDatasetUrl, setNextDatasetUrl] = useState(() => datasetUrl);
 
28
  const db = useDuckDb();
29
- const { loading, dataset, error, clearDataset } = useParquetTable(
30
- db,
31
- datasetUrl
 
 
 
32
  );
 
 
 
 
 
 
 
 
 
33
 
34
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
35
  e.preventDefault();
 
36
  setDatasetUrl(nextDatasetUrl);
 
37
  };
38
 
39
  return (
@@ -67,6 +83,10 @@ function App() {
67
  variant={"secondary"}
68
  type="button"
69
  onClick={() => {
 
 
 
 
70
  setNextDatasetUrl(DEFAULT_DATASET_URL);
71
  setDatasetUrl(DEFAULT_DATASET_URL);
72
  }}
@@ -86,12 +106,37 @@ function App() {
86
  </Button>
87
  </div>
88
  </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  <div className="flex flex-col gap-2 w-full max-w-full overflow-x-auto bg-secondary rounded">
90
  {loading && (
91
  <p className="text-lg font-medium self-center">Loading...</p>
92
  )}
93
  {error && <p style={{ color: "red" }}>{error}</p>}
94
- {dataset && <Table data={dataset} />}
 
 
 
 
95
  </div>
96
  </section>
97
  );
 
1
+ import { useCallback, useEffect, useState } from "react";
2
  import { Table } from "./table";
3
  import { Button } from "~/components/ui/button";
4
  import { Textarea } from "~/components/ui/textarea";
 
24
 
25
  function App() {
26
  const [datasetUrl, setDatasetUrl] = usePersistedTextfield("datasetUrl");
27
+ const [datasetQuery, setDatasetQuery] = usePersistedTextfield("datasetQuery");
28
  const [nextDatasetUrl, setNextDatasetUrl] = useState(() => datasetUrl);
29
+ const [nextDatasetQuery, setNextDatasetQuery] = useState(() => datasetQuery);
30
  const db = useDuckDb();
31
+ const onQueryChanged = useCallback(
32
+ (query: string) => {
33
+ console.log("Query changed", query);
34
+ setNextDatasetQuery(query);
35
+ },
36
+ [setNextDatasetQuery]
37
  );
38
+ useEffect(() => {
39
+ setNextDatasetQuery(datasetQuery);
40
+ }, [datasetQuery]);
41
+ const { loading, dataset, error, clearDataset } = useParquetTable(db, {
42
+ datasetUrl,
43
+ datasetQuery,
44
+ setDatasetQuery,
45
+ onQueryChanged,
46
+ });
47
 
48
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
49
  e.preventDefault();
50
+ setNextDatasetQuery("");
51
  setDatasetUrl(nextDatasetUrl);
52
+ setDatasetQuery("");
53
  };
54
 
55
  return (
 
83
  variant={"secondary"}
84
  type="button"
85
  onClick={() => {
86
+ if (datasetUrl !== DEFAULT_DATASET_URL) {
87
+ setDatasetQuery("");
88
+ setNextDatasetQuery("");
89
+ }
90
  setNextDatasetUrl(DEFAULT_DATASET_URL);
91
  setDatasetUrl(DEFAULT_DATASET_URL);
92
  }}
 
106
  </Button>
107
  </div>
108
  </form>
109
+ {dataset && (
110
+ <form
111
+ onSubmit={(e) => {
112
+ e.preventDefault();
113
+ setDatasetQuery(nextDatasetQuery);
114
+ }}
115
+ className="flex gap-2 w-full items-center"
116
+ >
117
+ <Textarea
118
+ rows={2}
119
+ name="textField"
120
+ onChange={(e) => setNextDatasetQuery(e.target.value)}
121
+ value={nextDatasetQuery}
122
+ className="w-full lg:w-full"
123
+ placeholder="SELECT * FROM dataset LIMIT 10;"
124
+ />
125
+ <Button type="submit" className="h-full">
126
+ Run Query
127
+ </Button>
128
+ </form>
129
+ )}
130
  <div className="flex flex-col gap-2 w-full max-w-full overflow-x-auto bg-secondary rounded">
131
  {loading && (
132
  <p className="text-lg font-medium self-center">Loading...</p>
133
  )}
134
  {error && <p style={{ color: "red" }}>{error}</p>}
135
+ {dataset && (
136
+ <div className="flex flex-col gap-2">
137
+ <Table data={dataset} />
138
+ </div>
139
+ )}
140
  </div>
141
  </section>
142
  );
src/useParquetTable.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { AsyncDuckDB, AsyncDuckDBConnection } from "@duckdb/duckdb-wasm";
2
- import { useCallback, useEffect, useState } from "react";
3
 
4
  // https://duckdb.org/docs/api/wasm/query#arrow-table-to-json
5
  // TODO: import the arrow lib and use the correct type
@@ -9,10 +9,40 @@ const arrowResultToJson = (arrowResult: any) => {
9
  return arrowResult.toArray().map((row: any) => row.toJSON());
10
  };
11
 
12
- export const useParquetTable = (db: AsyncDuckDB | null, datasetUrl: string) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  const [loading, setLoading] = useState(false);
 
14
  const [dataset, setDataset] = useState<null>(null);
15
  const [error, setError] = useState<string | null>(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  const clearDataset = useCallback(() => {
18
  setDataset(null);
@@ -24,6 +54,7 @@ export const useParquetTable = (db: AsyncDuckDB | null, datasetUrl: string) => {
24
  setLoading(true);
25
  setDataset(null);
26
  setError(null);
 
27
  console.log("Loading", datasetUrl);
28
  const status: { conn: AsyncDuckDBConnection | null; killed: boolean } = {
29
  conn: null,
@@ -41,13 +72,21 @@ export const useParquetTable = (db: AsyncDuckDB | null, datasetUrl: string) => {
41
  // https://duckdb.org/docs/api/wasm/extensions
42
  // https://duckdb.org/docs/api/wasm/data_ingestion#parquet
43
  .query(
44
- `LOAD parquet;LOAD httpfs;SELECT * FROM '${datasetUrl}' LIMIT 10`
45
  )
46
- .then((result) => setDataset(arrowResultToJson(result)))
 
 
 
 
 
 
47
  .catch((err) => {
48
  console.error(err);
49
  setError(err.message);
50
  setDataset(null);
 
 
51
  })
52
  .finally(() => {
53
  conn.close();
@@ -64,13 +103,48 @@ export const useParquetTable = (db: AsyncDuckDB | null, datasetUrl: string) => {
64
  };
65
  } else if (db) {
66
  console.log("Resetting db");
 
 
 
 
67
  db.reset();
68
  }
69
- }, [db, datasetUrl]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  useEffect(() => {
72
  loadDataset();
73
  }, [loadDataset]);
74
 
75
- return { loading, dataset, error, clearDataset, loadDataset };
 
 
 
 
 
 
 
 
76
  };
 
1
  import { AsyncDuckDB, AsyncDuckDBConnection } from "@duckdb/duckdb-wasm";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
 
4
  // https://duckdb.org/docs/api/wasm/query#arrow-table-to-json
5
  // TODO: import the arrow lib and use the correct type
 
9
  return arrowResult.toArray().map((row: any) => row.toJSON());
10
  };
11
 
12
+ export const useParquetTable = (
13
+ db: AsyncDuckDB | null,
14
+ {
15
+ datasetUrl,
16
+ datasetQuery,
17
+ setDatasetQuery: _setDatasetQuery,
18
+ onQueryChanged,
19
+ }: {
20
+ datasetUrl: string;
21
+ datasetQuery: string;
22
+ setDatasetQuery: (query: string) => void;
23
+ onQueryChanged?: (query: string) => void;
24
+ }
25
+ ) => {
26
+ const [datasetLoaded, setDatasetLoaded] = useState(false);
27
  const [loading, setLoading] = useState(false);
28
+ const [querying, setQuerying] = useState(false);
29
  const [dataset, setDataset] = useState<null>(null);
30
  const [error, setError] = useState<string | null>(null);
31
+ const onQueryChangedRef = useRef(onQueryChanged);
32
+ useEffect(() => {
33
+ onQueryChangedRef.current = onQueryChanged;
34
+ }, [onQueryChanged]);
35
+ const datasetQueryRef = useRef(datasetQuery);
36
+ useEffect(() => {
37
+ datasetQueryRef.current = datasetQuery;
38
+ }, [datasetQuery]);
39
+ const setDatasetQuery = useCallback(
40
+ (query: string) => {
41
+ _setDatasetQuery(query);
42
+ onQueryChangedRef.current?.(query);
43
+ },
44
+ [_setDatasetQuery, onQueryChangedRef]
45
+ );
46
 
47
  const clearDataset = useCallback(() => {
48
  setDataset(null);
 
54
  setLoading(true);
55
  setDataset(null);
56
  setError(null);
57
+ setDatasetLoaded(false);
58
  console.log("Loading", datasetUrl);
59
  const status: { conn: AsyncDuckDBConnection | null; killed: boolean } = {
60
  conn: null,
 
72
  // https://duckdb.org/docs/api/wasm/extensions
73
  // https://duckdb.org/docs/api/wasm/data_ingestion#parquet
74
  .query(
75
+ `LOAD parquet;LOAD httpfs;DROP TABLE IF EXISTS dataset;CREATE TABLE dataset AS SELECT * FROM '${datasetUrl}';`
76
  )
77
+ .then(() => {
78
+ setDatasetLoaded(true);
79
+ if (!datasetQueryRef.current) {
80
+ console.log("Setting default query");
81
+ setDatasetQuery(`SELECT * FROM dataset LIMIT 10;`);
82
+ }
83
+ })
84
  .catch((err) => {
85
  console.error(err);
86
  setError(err.message);
87
  setDataset(null);
88
+ setDatasetLoaded(false);
89
+ setDatasetQuery("");
90
  })
91
  .finally(() => {
92
  conn.close();
 
103
  };
104
  } else if (db) {
105
  console.log("Resetting db");
106
+ setDataset(null);
107
+ setError(null);
108
+ setDatasetLoaded(false);
109
+ setDatasetQuery("");
110
  db.reset();
111
  }
112
+ }, [db, datasetUrl, setDatasetQuery, datasetQueryRef]);
113
+
114
+ const performQuery = useCallback(
115
+ (query: string) => {
116
+ if (db && datasetLoaded) {
117
+ setQuerying(true);
118
+ db.connect()
119
+ .then((conn) => {
120
+ conn
121
+ .query(query)
122
+ .then((result) => setDataset(arrowResultToJson(result)))
123
+ .catch(setError);
124
+ })
125
+ .finally(() => setQuerying(false));
126
+ }
127
+ },
128
+ [db, datasetLoaded]
129
+ );
130
+
131
+ useEffect(() => {
132
+ if (datasetLoaded && datasetQuery) {
133
+ performQuery(datasetQuery);
134
+ }
135
+ }, [datasetLoaded, datasetQuery, performQuery]);
136
 
137
  useEffect(() => {
138
  loadDataset();
139
  }, [loadDataset]);
140
 
141
+ return {
142
+ loading,
143
+ dataset,
144
+ error,
145
+ clearDataset,
146
+ loadDataset,
147
+ querying,
148
+ performQuery,
149
+ };
150
  };