@sqlrooms/duckdb 0.29.0-rc.7 → 0.29.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +2 -1
- package/README.md +61 -4
- package/dist/DuckDbSlice.d.ts +20 -5
- package/dist/DuckDbSlice.d.ts.map +1 -1
- package/dist/DuckDbSlice.js +136 -49
- package/dist/DuckDbSlice.js.map +1 -1
- package/dist/connectors/WasmDuckDbConnector.d.ts.map +1 -1
- package/dist/connectors/WasmDuckDbConnector.js +8 -0
- package/dist/connectors/WasmDuckDbConnector.js.map +1 -1
- package/dist/connectors/WebSocketDuckDbConnector.d.ts +23 -0
- package/dist/connectors/WebSocketDuckDbConnector.d.ts.map +1 -1
- package/dist/connectors/WebSocketDuckDbConnector.js +62 -0
- package/dist/connectors/WebSocketDuckDbConnector.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/loadTableSchemas.d.ts +2 -0
- package/dist/loadTableSchemas.d.ts.map +1 -1
- package/dist/loadTableSchemas.js +18 -10
- package/dist/loadTableSchemas.js.map +1 -1
- package/dist/useDataTable.d.ts +14 -0
- package/dist/useDataTable.d.ts.map +1 -0
- package/dist/useDataTable.js +21 -0
- package/dist/useDataTable.js.map +1 -0
- package/package.json +7 -7
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright
|
|
3
|
+
Copyright 2024-2026 SQLRooms Contributors
|
|
4
|
+
Copyright Vis.gl contributors
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
7
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A powerful wrapper around DuckDB-WASM that provides React hooks and utilities fo
|
|
|
4
4
|
|
|
5
5
|
### React Integration & Type Safety
|
|
6
6
|
|
|
7
|
-
- **React Hooks**: Seamless integration with React applications via `useSql`
|
|
7
|
+
- **React Hooks**: Seamless integration with React applications via `useSql` and `useDataTable`
|
|
8
8
|
- **Runtime Validation**: Optional Zod schema validation for query results with type transformations
|
|
9
9
|
- **Typed Row Accessors**: Type-safe row access with validation and multiple iteration methods
|
|
10
10
|
|
|
@@ -57,6 +57,55 @@ function UserList() {
|
|
|
57
57
|
|
|
58
58
|
For more information and examples on using the `useSql` hook, see the [useSql API documentation](/api/duckdb/functions/useSql).
|
|
59
59
|
|
|
60
|
+
### Monitoring WebSocket DuckDB Connections
|
|
61
|
+
|
|
62
|
+
`createWebSocketDuckDbConnector()` exposes the persistent WebSocket lifecycle
|
|
63
|
+
through `connectionStatus`, `subscribeConnectionStatus()`, and the optional
|
|
64
|
+
`onConnectionStatusChange` callback. Use this for live UI affordances such as
|
|
65
|
+
lost-connection dialogs. Call `reconnect()` to reopen the socket and rerun the
|
|
66
|
+
connector initialization SQL without destroying the connector instance.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import {createWebSocketDuckDbConnector} from '@sqlrooms/duckdb';
|
|
70
|
+
|
|
71
|
+
const connector = createWebSocketDuckDbConnector({
|
|
72
|
+
wsUrl: 'ws://localhost:4000',
|
|
73
|
+
onConnectionStatusChange: (status) => {
|
|
74
|
+
console.log('DuckDB WebSocket status:', status);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const unsubscribe = connector.subscribeConnectionStatus((status) => {
|
|
79
|
+
if (status === 'disconnected') {
|
|
80
|
+
console.warn('DuckDB WebSocket disconnected');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await connector.reconnect();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Looking up Table Metadata
|
|
88
|
+
|
|
89
|
+
Use `useDataTable()` in React components or `db.findTable()` from the room
|
|
90
|
+
store. String references are parsed like SQL identifiers, so use quotes for
|
|
91
|
+
literal dots in table names.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import {useDataTable} from '@sqlrooms/duckdb';
|
|
95
|
+
|
|
96
|
+
function TableColumns() {
|
|
97
|
+
const table = useDataTable('"memory"."main"."earthquakes"');
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<ul>
|
|
101
|
+
{table?.columns.map((column) => (
|
|
102
|
+
<li key={column.name}>{column.name}</li>
|
|
103
|
+
))}
|
|
104
|
+
</ul>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
60
109
|
### Using Zod for Runtime Validation
|
|
61
110
|
|
|
62
111
|
```tsx
|
|
@@ -207,11 +256,12 @@ function DatabaseManager() {
|
|
|
207
256
|
### Working with Qualified Table Names
|
|
208
257
|
|
|
209
258
|
```tsx
|
|
210
|
-
import {
|
|
259
|
+
import {quoteTableReference, resolveTableReference} from '@sqlrooms/duckdb';
|
|
211
260
|
import {useRoomStore} from './store';
|
|
212
261
|
import {Button} from '@sqlrooms/ui';
|
|
213
262
|
|
|
214
263
|
function QualifiedTableOps() {
|
|
264
|
+
const qualifyTableName = useRoomStore((state) => state.db.qualifyTableName);
|
|
215
265
|
const createTableFromQuery = useRoomStore(
|
|
216
266
|
(state) => state.db.createTableFromQuery,
|
|
217
267
|
);
|
|
@@ -219,14 +269,21 @@ function QualifiedTableOps() {
|
|
|
219
269
|
const checkTableExists = useRoomStore((state) => state.db.checkTableExists);
|
|
220
270
|
|
|
221
271
|
const run = async () => {
|
|
222
|
-
//
|
|
223
|
-
const qualifiedTable =
|
|
272
|
+
// Store-aware qualification knows which database is the default.
|
|
273
|
+
const qualifiedTable = qualifyTableName({
|
|
224
274
|
database: 'mydb',
|
|
225
275
|
schema: 'public',
|
|
226
276
|
table: 'users',
|
|
227
277
|
});
|
|
278
|
+
// toString() is the canonical portable table ID; toFullString() includes
|
|
279
|
+
// the database when explicit catalog qualification is needed.
|
|
280
|
+
const tableSql = quoteTableReference(qualifiedTable.toString());
|
|
281
|
+
const resolved = resolveTableReference([{table: qualifiedTable}], 'users');
|
|
228
282
|
|
|
229
283
|
await createTableFromQuery(qualifiedTable, 'SELECT * FROM source_table');
|
|
284
|
+
console.log('Quoted table reference:', tableSql);
|
|
285
|
+
console.log('Fully qualified reference:', qualifiedTable.toFullString());
|
|
286
|
+
console.log('Resolved table:', resolved.table?.table.toString());
|
|
230
287
|
const tableExists = await checkTableExists(qualifiedTable);
|
|
231
288
|
console.log('Table exists after create:', tableExists);
|
|
232
289
|
await dropTable(qualifiedTable);
|
package/dist/DuckDbSlice.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export type DuckDbSliceState = {
|
|
|
34
34
|
schema: string;
|
|
35
35
|
currentSchema: string | undefined;
|
|
36
36
|
currentDatabase: string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Create a context-aware qualified table name.
|
|
39
|
+
*
|
|
40
|
+
* String inputs are treated as table identifier names, not SQL references;
|
|
41
|
+
* use findTable() when resolving an existing table reference.
|
|
42
|
+
*/
|
|
43
|
+
qualifyTableName(tableName: string | QualifiedTableNameInput): QualifiedTableName;
|
|
37
44
|
/**
|
|
38
45
|
* Cache of refreshed table schemas
|
|
39
46
|
*/
|
|
@@ -84,7 +91,7 @@ export type DuckDbSliceState = {
|
|
|
84
91
|
*/
|
|
85
92
|
loadTableSchemas(filter?: LoadTableSchemasFilter): Promise<DataTable[]>;
|
|
86
93
|
/**
|
|
87
|
-
* @deprecated Use
|
|
94
|
+
* @deprecated Use findTable instead
|
|
88
95
|
*/
|
|
89
96
|
getTable(tableName: string): DataTable | undefined;
|
|
90
97
|
/**
|
|
@@ -92,12 +99,18 @@ export type DuckDbSliceState = {
|
|
|
92
99
|
*/
|
|
93
100
|
setTableRowCount(tableName: string | QualifiedTableName, rowCount: number): void;
|
|
94
101
|
/**
|
|
95
|
-
* Find a table by
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
102
|
+
* Find a table by reference in the last refreshed table schemas.
|
|
103
|
+
* String references are parsed as SQL identifiers, so dots separate
|
|
104
|
+
* database/schema/table segments unless quoted. If no schema or database is
|
|
105
|
+
* provided, the table is resolved in the current schema and database from
|
|
106
|
+
* the last schema refresh.
|
|
107
|
+
* @param tableName - The table reference to find.
|
|
99
108
|
* @returns The table or undefined if not found.
|
|
100
109
|
*/
|
|
110
|
+
findTable(tableName: string | QualifiedTableName): DataTable | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* @deprecated Use findTable instead
|
|
113
|
+
*/
|
|
101
114
|
findTableByName(tableName: string | QualifiedTableName): DataTable | undefined;
|
|
102
115
|
/**
|
|
103
116
|
* Refresh table schemas from the database.
|
|
@@ -192,6 +205,7 @@ export type DuckDbSliceState = {
|
|
|
192
205
|
}>;
|
|
193
206
|
};
|
|
194
207
|
};
|
|
208
|
+
type QualifiedTableNameInput = Pick<QualifiedTableName, 'database' | 'schema' | 'table' | 'defaultDatabase'>;
|
|
195
209
|
export type CreateDuckDbSliceProps = {
|
|
196
210
|
connector?: DuckDbConnector;
|
|
197
211
|
/**
|
|
@@ -222,4 +236,5 @@ export declare function createDuckDbSlice({ connector, loadTableSchemasFilter, l
|
|
|
222
236
|
* @returns The selected value of type `T`
|
|
223
237
|
*/
|
|
224
238
|
export declare function useStoreWithDuckDb<T>(selector: (state: BaseRoomStoreState & DuckDbSliceState) => T): T;
|
|
239
|
+
export {};
|
|
225
240
|
//# sourceMappingURL=DuckDbSlice.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DuckDbSlice.d.ts","sourceRoot":"","sources":["../src/DuckDbSlice.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,YAAY,EACZ,eAAe,
|
|
1
|
+
{"version":3,"file":"DuckDbSlice.d.ts","sourceRoot":"","sources":["../src/DuckDbSlice.ts"],"names":[],"mappings":"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"}
|
package/dist/DuckDbSlice.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDbSchemaTrees, escapeVal, getColValAsNumber, isQualifiedTableName, joinStatements, makeQualifiedTableName, separateLastStatement, } from '@sqlrooms/duckdb-core';
|
|
1
|
+
import { createDbSchemaTrees, escapeVal, getColValAsNumber, isQualifiedTableName, joinStatements, makeQualifiedTableName, parseQualifiedSqlIdentifier, separateLastStatement, } from '@sqlrooms/duckdb-core';
|
|
2
2
|
import { createSlice, registerCommandsForOwner, unregisterCommandsForOwner, useBaseRoomStore, } from '@sqlrooms/room-store';
|
|
3
3
|
import * as arrow from 'apache-arrow';
|
|
4
4
|
import deepEquals from 'fast-deep-equal';
|
|
@@ -15,7 +15,7 @@ const DUCKDB_TEMP_DATABASE = 'temp';
|
|
|
15
15
|
* Hides `__sqlrooms_*` names and DuckDB's `temp` database.
|
|
16
16
|
*/
|
|
17
17
|
export const defaultLoadTableSchemasFilter = (table) => {
|
|
18
|
-
if (table.table
|
|
18
|
+
if (table.table.startsWith(INTERNAL_SQLROOMS_PREFIX) ||
|
|
19
19
|
table.database?.startsWith(INTERNAL_SQLROOMS_PREFIX) ||
|
|
20
20
|
table.schema?.startsWith(INTERNAL_SQLROOMS_PREFIX)) {
|
|
21
21
|
return false;
|
|
@@ -95,17 +95,55 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
95
95
|
return defaultLoadSchemaCatalogFilter(entry);
|
|
96
96
|
}));
|
|
97
97
|
return createSlice((set, get, store) => {
|
|
98
|
+
const parseTableReferenceParts = (tableName) => {
|
|
99
|
+
const parsed = parseQualifiedSqlIdentifier(tableName);
|
|
100
|
+
return parsed?.table
|
|
101
|
+
? {
|
|
102
|
+
database: parsed.database,
|
|
103
|
+
schema: parsed.schema,
|
|
104
|
+
table: parsed.table,
|
|
105
|
+
}
|
|
106
|
+
: { table: tableName };
|
|
107
|
+
};
|
|
98
108
|
/**
|
|
99
109
|
* Internal helper to load a table schema by exact name, bypassing the visibility filter.
|
|
100
110
|
* Used when performing exact lookups (e.g., checking if a specific table exists).
|
|
101
111
|
*/
|
|
102
112
|
const loadTableSchemaByName = async (tableName) => {
|
|
103
|
-
const
|
|
104
|
-
? tableName
|
|
105
|
-
:
|
|
113
|
+
const tableNameParts = typeof tableName === 'string'
|
|
114
|
+
? parseTableReferenceParts(tableName)
|
|
115
|
+
: tableName;
|
|
116
|
+
const qualifiedName = get().db.qualifyTableName(tableNameParts);
|
|
106
117
|
const connector = await get().db.getConnector();
|
|
107
|
-
const [table] = await loadTableSchemas(connector,
|
|
108
|
-
|
|
118
|
+
const [table] = await loadTableSchemas(connector, {
|
|
119
|
+
database: qualifiedName.database,
|
|
120
|
+
schema: qualifiedName.schema,
|
|
121
|
+
table: qualifiedName.table,
|
|
122
|
+
defaultDatabase: get().db.currentDatabase,
|
|
123
|
+
});
|
|
124
|
+
return table &&
|
|
125
|
+
table.table.table === qualifiedName.table &&
|
|
126
|
+
(!qualifiedName.schema ||
|
|
127
|
+
table.table.schema === qualifiedName.schema) &&
|
|
128
|
+
(!qualifiedName.database ||
|
|
129
|
+
table.table.database === qualifiedName.database)
|
|
130
|
+
? table
|
|
131
|
+
: undefined;
|
|
132
|
+
};
|
|
133
|
+
const throwIfUnresolvedExplicitDatabaseReference = (tableName, table) => {
|
|
134
|
+
if (table) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const database = typeof tableName === 'string'
|
|
138
|
+
? parseTableReferenceParts(tableName).database
|
|
139
|
+
: tableName.database;
|
|
140
|
+
if (!database) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const qualifiedName = isQualifiedTableName(tableName)
|
|
144
|
+
? get().db.qualifyTableName(tableName)
|
|
145
|
+
: get().db.qualifyTableName(parseTableReferenceParts(tableName));
|
|
146
|
+
throw new Error(`Relation "${qualifiedName}" not found.`);
|
|
109
147
|
};
|
|
110
148
|
return {
|
|
111
149
|
db: {
|
|
@@ -118,6 +156,16 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
118
156
|
tableRowCounts: {},
|
|
119
157
|
schemaTrees: undefined,
|
|
120
158
|
queryCache: {},
|
|
159
|
+
qualifyTableName(tableName) {
|
|
160
|
+
const { currentDatabase, currentSchema } = get().db;
|
|
161
|
+
const parts = typeof tableName === 'string' ? { table: tableName } : tableName;
|
|
162
|
+
return makeQualifiedTableName({
|
|
163
|
+
database: parts.database ?? currentDatabase,
|
|
164
|
+
schema: parts.schema ?? currentSchema,
|
|
165
|
+
table: parts.table,
|
|
166
|
+
defaultDatabase: parts.defaultDatabase ?? currentDatabase,
|
|
167
|
+
});
|
|
168
|
+
},
|
|
121
169
|
setConnector: (connector) => {
|
|
122
170
|
set(produce((state) => {
|
|
123
171
|
state.config.dataSources = [];
|
|
@@ -172,14 +220,13 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
172
220
|
async createTableFromQuery(tableName, query, options) {
|
|
173
221
|
const { replace = true, temp = false, view = false, allowMultipleStatements = false, abortSignal, } = options || {};
|
|
174
222
|
// For temp tables/views, DuckDB requires the "temp" database
|
|
175
|
-
const baseQualifiedName =
|
|
176
|
-
? tableName
|
|
177
|
-
: makeQualifiedTableName({ table: tableName });
|
|
223
|
+
const baseQualifiedName = get().db.qualifyTableName(tableName);
|
|
178
224
|
const qualifiedName = temp
|
|
179
225
|
? makeQualifiedTableName({
|
|
180
226
|
table: baseQualifiedName.table,
|
|
181
227
|
schema: baseQualifiedName.schema,
|
|
182
228
|
database: 'temp',
|
|
229
|
+
defaultDatabase: get().db.currentDatabase,
|
|
183
230
|
})
|
|
184
231
|
: baseQualifiedName;
|
|
185
232
|
const connector = await get().db.getConnector();
|
|
@@ -233,14 +280,14 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
233
280
|
* @deprecated Use .loadTableRowCount() instead
|
|
234
281
|
*/
|
|
235
282
|
async getTableRowCount(table, schema = 'main') {
|
|
236
|
-
return get().db.loadTableRowCount({ table, schema });
|
|
283
|
+
return get().db.loadTableRowCount(get().db.qualifyTableName({ table, schema }));
|
|
237
284
|
},
|
|
238
285
|
async loadTableRowCount(tableName) {
|
|
239
286
|
const { schema, database, table } = typeof tableName === 'string'
|
|
240
|
-
?
|
|
241
|
-
: tableName
|
|
287
|
+
? get().db.qualifyTableName(parseTableReferenceParts(tableName))
|
|
288
|
+
: get().db.qualifyTableName(tableName);
|
|
242
289
|
const connector = await get().db.getConnector();
|
|
243
|
-
const result = await connector.query(`SELECT COUNT(*) FROM ${
|
|
290
|
+
const result = await connector.query(`SELECT COUNT(*) FROM ${get().db.qualifyTableName({
|
|
244
291
|
schema,
|
|
245
292
|
database,
|
|
246
293
|
table,
|
|
@@ -258,19 +305,23 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
258
305
|
return loadTableSchemas(connector, {
|
|
259
306
|
...filter,
|
|
260
307
|
filterFunction: loadTableSchemasFilter,
|
|
308
|
+
defaultDatabase: get().db.currentDatabase,
|
|
261
309
|
});
|
|
262
310
|
},
|
|
263
311
|
async checkTableExists(tableName) {
|
|
264
|
-
const table =
|
|
312
|
+
const table = get().db.findTable(tableName) ??
|
|
313
|
+
(await loadTableSchemaByName(tableName));
|
|
265
314
|
return Boolean(table);
|
|
266
315
|
},
|
|
267
316
|
async dropRelation(tableName) {
|
|
268
317
|
const connector = await get().db.getConnector();
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
(
|
|
318
|
+
const table = get().db.findTable(tableName) ??
|
|
319
|
+
(await loadTableSchemaByName(tableName));
|
|
320
|
+
throwIfUnresolvedExplicitDatabaseReference(tableName, table);
|
|
321
|
+
const qualifiedTable = table?.table ??
|
|
322
|
+
(isQualifiedTableName(tableName)
|
|
323
|
+
? get().db.qualifyTableName(tableName)
|
|
324
|
+
: get().db.qualifyTableName(parseTableReferenceParts(tableName)));
|
|
274
325
|
const isView = table?.isView;
|
|
275
326
|
if (isView) {
|
|
276
327
|
await connector.query(`DROP VIEW IF EXISTS ${qualifiedTable};`);
|
|
@@ -282,11 +333,13 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
282
333
|
},
|
|
283
334
|
async dropTable(tableName) {
|
|
284
335
|
const connector = await get().db.getConnector();
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
(
|
|
336
|
+
const table = get().db.findTable(tableName) ??
|
|
337
|
+
(await loadTableSchemaByName(tableName));
|
|
338
|
+
throwIfUnresolvedExplicitDatabaseReference(tableName, table);
|
|
339
|
+
const qualifiedTable = table?.table ??
|
|
340
|
+
(isQualifiedTableName(tableName)
|
|
341
|
+
? get().db.qualifyTableName(tableName)
|
|
342
|
+
: get().db.qualifyTableName(parseTableReferenceParts(tableName)));
|
|
290
343
|
if (table?.isView) {
|
|
291
344
|
throw new Error(`"${qualifiedTable}" is a view. Use dropRelation() to remove views.`);
|
|
292
345
|
}
|
|
@@ -294,9 +347,7 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
294
347
|
get().db.refreshTableSchemas();
|
|
295
348
|
},
|
|
296
349
|
async addTable(tableName, data) {
|
|
297
|
-
const qualifiedName =
|
|
298
|
-
? tableName
|
|
299
|
-
: makeQualifiedTableName({ table: tableName });
|
|
350
|
+
const qualifiedName = get().db.qualifyTableName(tableName);
|
|
300
351
|
const { db } = get();
|
|
301
352
|
if (data instanceof arrow.Table) {
|
|
302
353
|
// TODO: make sure the table is replaced
|
|
@@ -318,27 +369,51 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
318
369
|
return newTable;
|
|
319
370
|
},
|
|
320
371
|
async setTableRowCount(tableName, rowCount) {
|
|
321
|
-
const qualifiedName =
|
|
322
|
-
? tableName
|
|
323
|
-
: makeQualifiedTableName({ table: tableName });
|
|
372
|
+
const qualifiedName = get().db.qualifyTableName(tableName);
|
|
324
373
|
set((state) => produce(state, (draft) => {
|
|
325
374
|
draft.db.tableRowCounts[qualifiedName.toString()] = rowCount;
|
|
326
375
|
}));
|
|
327
376
|
},
|
|
328
377
|
getTable(tableName) {
|
|
329
|
-
return get().db.
|
|
378
|
+
return get().db.findTable(tableName);
|
|
330
379
|
},
|
|
331
|
-
|
|
332
|
-
const {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
380
|
+
findTable(tableName) {
|
|
381
|
+
const { currentSchema, currentDatabase, tables } = get().db;
|
|
382
|
+
const findMatchingTable = ({ table, schema, database, }) => table
|
|
383
|
+
? tables.find((t) => t.table.table === table &&
|
|
384
|
+
(!schema || t.table.schema === schema) &&
|
|
385
|
+
(!database || t.table.database === database))
|
|
386
|
+
: undefined;
|
|
387
|
+
const findUniqueMatchingTable = ({ table, schema, }) => {
|
|
388
|
+
if (!table)
|
|
389
|
+
return undefined;
|
|
390
|
+
const matches = tables.filter((t) => t.table.table === table &&
|
|
391
|
+
(!schema || t.table.schema === schema));
|
|
392
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
393
|
+
};
|
|
394
|
+
const resolvedTableName = typeof tableName === 'string'
|
|
395
|
+
? (parseQualifiedSqlIdentifier(tableName) ?? {})
|
|
396
|
+
: tableName;
|
|
397
|
+
const { table, schema, database, defaultDatabase } = {
|
|
398
|
+
schema: currentSchema,
|
|
399
|
+
database: currentDatabase,
|
|
400
|
+
...resolvedTableName,
|
|
338
401
|
};
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
402
|
+
const exactMatch = findMatchingTable({ table, schema, database });
|
|
403
|
+
if (exactMatch)
|
|
404
|
+
return exactMatch;
|
|
405
|
+
const isStaleDefaultDatabaseReference = typeof tableName !== 'string' &&
|
|
406
|
+
database &&
|
|
407
|
+
defaultDatabase &&
|
|
408
|
+
database === defaultDatabase &&
|
|
409
|
+
database !== currentDatabase;
|
|
410
|
+
if (isStaleDefaultDatabaseReference) {
|
|
411
|
+
return findUniqueMatchingTable({ table, schema });
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
414
|
+
},
|
|
415
|
+
findTableByName(tableName) {
|
|
416
|
+
return get().db.findTable(tableName);
|
|
342
417
|
},
|
|
343
418
|
async refreshTableSchemas() {
|
|
344
419
|
if (refreshPromise) {
|
|
@@ -355,16 +430,23 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
355
430
|
pendingSchemaRefresh = false;
|
|
356
431
|
const connector = await get().db.getConnector();
|
|
357
432
|
const result = await connector.query(`SELECT current_schema() AS schema, current_database() AS database`);
|
|
433
|
+
const currentSchemaValue = result.getChild('schema')?.get(0);
|
|
434
|
+
const currentDatabaseValue = result
|
|
435
|
+
.getChild('database')
|
|
436
|
+
?.get(0);
|
|
437
|
+
const currentSchema = currentSchemaValue == null
|
|
438
|
+
? undefined
|
|
439
|
+
: String(currentSchemaValue);
|
|
440
|
+
const currentDatabase = currentDatabaseValue == null
|
|
441
|
+
? undefined
|
|
442
|
+
: String(currentDatabaseValue);
|
|
358
443
|
set((state) => produce(state, (draft) => {
|
|
359
|
-
draft.db.currentSchema =
|
|
360
|
-
|
|
361
|
-
?.get(0);
|
|
362
|
-
draft.db.currentDatabase = result
|
|
363
|
-
.getChild('database')
|
|
364
|
-
?.get(0);
|
|
444
|
+
draft.db.currentSchema = currentSchema;
|
|
445
|
+
draft.db.currentDatabase = currentDatabase;
|
|
365
446
|
}));
|
|
366
447
|
schemasWithTables = await loadSchemaCatalog(connector, {
|
|
367
448
|
filterFunction: effectiveSchemaCatalogFilter,
|
|
449
|
+
defaultDatabase: currentDatabase,
|
|
368
450
|
});
|
|
369
451
|
} while (pendingSchemaRefresh);
|
|
370
452
|
const newTables = schemasWithTables.flatMap((s) => s.tables);
|
|
@@ -414,7 +496,12 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loa
|
|
|
414
496
|
set((state) => produce(state, (draft) => {
|
|
415
497
|
draft.db.queryCache[queryKey] = queryHandle;
|
|
416
498
|
}));
|
|
417
|
-
queryHandle.result
|
|
499
|
+
queryHandle.result
|
|
500
|
+
.catch(() => {
|
|
501
|
+
// Prevent unhandled promise rejection warnings.
|
|
502
|
+
// Callers handle errors via await/try-catch.
|
|
503
|
+
})
|
|
504
|
+
.finally(() => {
|
|
418
505
|
// remove from cache after completion
|
|
419
506
|
set((state) => produce(state, (draft) => {
|
|
420
507
|
delete draft.db.queryCache[queryKey];
|