@sqlrooms/duckdb 0.29.0-rc.1 → 0.29.0-rc.2

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.
@@ -1,11 +1,12 @@
1
1
  import { DataTable, DbSchemaNode, DuckDbConnector, QualifiedTableName, QueryHandle } from '@sqlrooms/duckdb-core';
2
+ import { LoadTableSchemasFilter, LoadTableSchemasFilterFunction } from './loadTableSchemas';
2
3
  import { BaseRoomStoreState } from '@sqlrooms/room-store';
3
4
  import * as arrow from 'apache-arrow';
4
5
  import { StateCreator } from 'zustand';
5
- export type SchemaAndDatabase = {
6
- schema?: string;
7
- database?: string;
8
- };
6
+ /**
7
+ * Default filter to exclude internal SQLRooms tables, schemas, and databases
8
+ */
9
+ export declare const createDefaultLoadTableSchemasFilter: (table: QualifiedTableName) => boolean;
9
10
  /**
10
11
  * State and actions for the DuckDB slice
11
12
  */
@@ -69,9 +70,7 @@ export type DuckDbSliceState = {
69
70
  /**
70
71
  * Load the schemas of the tables in the database.
71
72
  */
72
- loadTableSchemas(filter?: SchemaAndDatabase & {
73
- table?: string;
74
- }): Promise<DataTable[]>;
73
+ loadTableSchemas(filter?: LoadTableSchemasFilter): Promise<DataTable[]>;
75
74
  /**
76
75
  * @deprecated Use findTableByName instead
77
76
  */
@@ -128,7 +127,12 @@ export type DuckDbSliceState = {
128
127
  */
129
128
  checkTableExists: (tableName: string | QualifiedTableName) => Promise<boolean>;
130
129
  /**
131
- * Delete a table with optional schema and database
130
+ * Delete a table or view with optional schema and database.
131
+ * @param tableName - The name of the relation to delete (qualified or plain)
132
+ */
133
+ dropRelation: (tableName: string | QualifiedTableName) => Promise<void>;
134
+ /**
135
+ * Delete a table with optional schema and database.
132
136
  * @param tableName - The name of the table to delete (qualified or plain)
133
137
  */
134
138
  dropTable: (tableName: string | QualifiedTableName) => Promise<void>;
@@ -178,11 +182,18 @@ export type DuckDbSliceState = {
178
182
  };
179
183
  export type CreateDuckDbSliceProps = {
180
184
  connector?: DuckDbConnector;
185
+ /**
186
+ * Optional filter function to control which tables are included when loading schemas.
187
+ * By default, filters out tables/schemas/databases starting with '__sqlrooms_'.
188
+ * @param table - The qualified table name to evaluate
189
+ * @returns true to include the table, false to exclude it
190
+ */
191
+ loadTableSchemasFilter?: LoadTableSchemasFilterFunction | null;
181
192
  };
182
193
  /**
183
194
  * Create a DuckDB slice for managing the connector
184
195
  */
185
- export declare function createDuckDbSlice({ connector, }?: CreateDuckDbSliceProps): StateCreator<DuckDbSliceState>;
196
+ export declare function createDuckDbSlice({ connector, loadTableSchemasFilter, }?: CreateDuckDbSliceProps): StateCreator<DuckDbSliceState>;
186
197
  /**
187
198
  * @internal
188
199
  * Select values from the room store that includes the DuckDB slice.
@@ -1 +1 @@
1
- {"version":3,"file":"DuckDbSlice.d.ts","sourceRoot":"","sources":["../src/DuckDbSlice.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DuckDbSlice.d.ts","sourceRoot":"","sources":["../src/DuckDbSlice.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { createDbSchemaTrees, escapeVal, getColValAsNumber, isQualifiedTableName, joinStatements, makeQualifiedTableName, separateLastStatement, } from '@sqlrooms/duckdb-core';
2
+ import { loadTableSchemas, } from './loadTableSchemas';
2
3
  import { createSlice, registerCommandsForOwner, unregisterCommandsForOwner, useBaseRoomStore, } from '@sqlrooms/room-store';
3
4
  import * as arrow from 'apache-arrow';
4
5
  import deepEquals from 'fast-deep-equal';
@@ -6,6 +7,15 @@ import { produce } from 'immer';
6
7
  import { z } from 'zod';
7
8
  import { createWasmDuckDbConnector } from './connectors/createDuckDbConnector';
8
9
  const DUCKDB_COMMAND_OWNER = '@sqlrooms/duckdb';
10
+ const INTERNAL_SQLROOMS_PREFIX = '__sqlrooms_';
11
+ /**
12
+ * Default filter to exclude internal SQLRooms tables, schemas, and databases
13
+ */
14
+ export const createDefaultLoadTableSchemasFilter = (table) => {
15
+ return (!table.table?.startsWith(INTERNAL_SQLROOMS_PREFIX) &&
16
+ !table.database?.startsWith(INTERNAL_SQLROOMS_PREFIX) &&
17
+ !table.schema?.startsWith(INTERNAL_SQLROOMS_PREFIX));
18
+ };
9
19
  const DropTableCommandInput = z.object({
10
20
  tableName: z.string().describe('Name of the table to drop.'),
11
21
  });
@@ -33,14 +43,25 @@ const CreateTableFromQueryCommandInput = z.object({
33
43
  .default(false)
34
44
  .describe('Allow multiple SQL statements where the final one is SELECT.'),
35
45
  });
36
- function isDuckDbPlaceholderViewColumn(columnName, columnType) {
37
- return columnName === '__' && columnType.toUpperCase() === 'UNKNOWN';
38
- }
39
46
  /**
40
47
  * Create a DuckDB slice for managing the connector
41
48
  */
42
- export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), } = {}) {
49
+ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), loadTableSchemasFilter = createDefaultLoadTableSchemasFilter, } = {}) {
50
+ let refreshPromise = null;
51
+ let pendingSchemaRefresh = false;
43
52
  return createSlice((set, get, store) => {
53
+ /**
54
+ * Internal helper to load a table schema by exact name, bypassing the visibility filter.
55
+ * Used when performing exact lookups (e.g., checking if a specific table exists).
56
+ */
57
+ const loadTableSchemaByName = async (tableName) => {
58
+ const qualifiedName = isQualifiedTableName(tableName)
59
+ ? tableName
60
+ : makeQualifiedTableName({ table: tableName });
61
+ const connector = await get().db.getConnector();
62
+ const [table] = await loadTableSchemas(connector, qualifiedName);
63
+ return table;
64
+ };
44
65
  return {
45
66
  db: {
46
67
  connector, // Will be initialized during init
@@ -60,7 +81,9 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), } =
60
81
  },
61
82
  initialize: async () => {
62
83
  await get().db.connector.initialize();
63
- await get().db.refreshTableSchemas();
84
+ // No await here, we want to continue initializing the room even
85
+ // if the table schemas are not refreshed yet
86
+ get().db.refreshTableSchemas();
64
87
  registerCommandsForOwner(store, DUCKDB_COMMAND_OWNER, createDuckDbCommands());
65
88
  },
66
89
  getConnector: async () => {
@@ -69,6 +92,18 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), } =
69
92
  },
70
93
  destroy: async () => {
71
94
  unregisterCommandsForOwner(store, DUCKDB_COMMAND_OWNER);
95
+ // Wait for any in-flight refreshTableSchemas() to finish before
96
+ // destroying the connector. initialize() fires it without await,
97
+ // so it may still be querying the native DuckDB instance; tearing
98
+ // that down mid-flight causes a use-after-free segfault.
99
+ if (refreshPromise) {
100
+ try {
101
+ await refreshPromise;
102
+ }
103
+ catch {
104
+ // ignore – we're shutting down
105
+ }
106
+ }
72
107
  try {
73
108
  if (get().db.connector) {
74
109
  await get().db.connector.destroy();
@@ -174,108 +209,44 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), } =
174
209
  return await get().db.loadTableSchemas({ schema });
175
210
  },
176
211
  async loadTableSchemas(filter) {
177
- const { schema, database, table } = filter || {};
178
- const sql = `WITH tables_and_views AS (
179
- FROM duckdb_tables() SELECT
180
- database_name AS database,
181
- schema_name AS schema,
182
- table_name AS name,
183
- sql,
184
- comment,
185
- estimated_size,
186
- FALSE AS isView
187
- UNION
188
- FROM duckdb_views() SELECT
189
- database_name AS database,
190
- schema_name AS schema,
191
- view_name AS name,
192
- sql,
193
- comment,
194
- NULL estimated_size,
195
- TRUE AS isView
196
- )
197
- SELECT
198
- isView,
199
- database, schema,
200
- name, column_names, column_types,
201
- sql, comment,
202
- estimated_size
203
- FROM (DESCRIBE)
204
- LEFT OUTER JOIN tables_and_views USING (database, schema, name)
205
- ${schema || database || table
206
- ? `WHERE ${[
207
- schema ? `schema = ${escapeVal(schema)}` : '',
208
- database ? `database = ${escapeVal(database)}` : '',
209
- table ? `name = ${escapeVal(table)}` : '',
210
- ]
211
- .filter(Boolean)
212
- .join(' AND ')}`
213
- : ''}`;
214
- const describeResults = await connector.query(sql);
215
- const newTables = [];
216
- for (let i = 0; i < describeResults.numRows; i++) {
217
- const isView = describeResults.getChild('isView')?.get(i);
218
- const database = describeResults.getChild('database')?.get(i);
219
- const schema = describeResults.getChild('schema')?.get(i);
220
- const table = describeResults.getChild('name')?.get(i);
221
- const sql = describeResults.getChild('sql')?.get(i);
222
- const comment = describeResults.getChild('comment')?.get(i);
223
- const estimatedSize = describeResults
224
- .getChild('estimated_size')
225
- ?.get(i);
226
- const columnNames = describeResults
227
- .getChild('column_names')
228
- ?.get(i);
229
- const columnTypes = describeResults
230
- .getChild('column_types')
231
- ?.get(i);
232
- const columns = [];
233
- for (let di = 0; di < (columnNames?.length ?? 0); di++) {
234
- const columnName = String(columnNames.get(di));
235
- const columnType = String(columnTypes?.get(di));
236
- if (isDuckDbPlaceholderViewColumn(columnName, columnType)) {
237
- continue;
238
- }
239
- columns.push({
240
- name: columnName,
241
- type: columnType,
242
- });
243
- }
244
- newTables.push({
245
- table: makeQualifiedTableName({ database, schema, table }),
246
- database,
247
- schema,
248
- tableName: table,
249
- columns,
250
- sql,
251
- comment,
252
- isView: Boolean(isView),
253
- rowCount: typeof estimatedSize === 'bigint'
254
- ? Number(estimatedSize)
255
- : estimatedSize === null
256
- ? undefined
257
- : estimatedSize,
258
- });
259
- }
260
- return newTables;
212
+ const connector = await get().db.getConnector();
213
+ return loadTableSchemas(connector, {
214
+ ...filter,
215
+ filterFunction: loadTableSchemasFilter,
216
+ });
261
217
  },
262
218
  async checkTableExists(tableName) {
263
- const qualifiedName = isQualifiedTableName(tableName)
219
+ const table = await loadTableSchemaByName(tableName);
220
+ return Boolean(table);
221
+ },
222
+ async dropRelation(tableName) {
223
+ const connector = await get().db.getConnector();
224
+ const qualifiedTable = isQualifiedTableName(tableName)
264
225
  ? tableName
265
226
  : makeQualifiedTableName({ table: tableName });
266
- const table = (await get().db.loadTableSchemas(qualifiedName))[0];
267
- if (!table) {
268
- return false;
227
+ const table = get().db.findTableByName(qualifiedTable) ??
228
+ (await loadTableSchemaByName(qualifiedTable));
229
+ const isView = table?.isView;
230
+ if (isView) {
231
+ await connector.query(`DROP VIEW IF EXISTS ${qualifiedTable};`);
269
232
  }
270
- return true;
233
+ else {
234
+ await connector.query(`DROP TABLE IF EXISTS ${qualifiedTable};`);
235
+ }
236
+ get().db.refreshTableSchemas();
271
237
  },
272
238
  async dropTable(tableName) {
273
239
  const connector = await get().db.getConnector();
274
240
  const qualifiedTable = isQualifiedTableName(tableName)
275
241
  ? tableName
276
242
  : makeQualifiedTableName({ table: tableName });
243
+ const table = get().db.findTableByName(qualifiedTable) ??
244
+ (await loadTableSchemaByName(qualifiedTable));
245
+ if (table?.isView) {
246
+ throw new Error(`"${qualifiedTable}" is a view. Use dropRelation() to remove views.`);
247
+ }
277
248
  await connector.query(`DROP TABLE IF EXISTS ${qualifiedTable};`);
278
- await get().db.refreshTableSchemas();
249
+ get().db.refreshTableSchemas();
279
250
  },
280
251
  async addTable(tableName, data) {
281
252
  const qualifiedName = isQualifiedTableName(tableName)
@@ -291,14 +262,14 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), } =
291
262
  replace: true,
292
263
  });
293
264
  }
294
- const newTable = (await db.loadTableSchemas(qualifiedName))[0];
265
+ const newTable = await loadTableSchemaByName(qualifiedName);
295
266
  if (!newTable) {
296
267
  throw new Error('Failed to add table');
297
268
  }
298
269
  set((state) => produce(state, (draft) => {
299
270
  draft.db.tables.push(newTable);
300
271
  }));
301
- await get().db.refreshTableSchemas();
272
+ get().db.refreshTableSchemas();
302
273
  return newTable;
303
274
  },
304
275
  async setTableRowCount(tableName, rowCount) {
@@ -325,37 +296,50 @@ export function createDuckDbSlice({ connector = createWasmDuckDbConnector(), } =
325
296
  (!database || t.table.database === database));
326
297
  },
327
298
  async refreshTableSchemas() {
299
+ if (refreshPromise) {
300
+ pendingSchemaRefresh = true;
301
+ return refreshPromise;
302
+ }
328
303
  set((state) => produce(state, (draft) => {
329
304
  draft.db.isRefreshingTableSchemas = true;
330
305
  }));
331
- try {
332
- const connector = await get().db.getConnector();
333
- const result = await connector.query(`SELECT current_schema() AS schema, current_database() AS database`);
334
- set((state) => produce(state, (draft) => {
335
- draft.db.currentSchema = result.getChild('schema')?.get(0);
336
- draft.db.currentDatabase = result
337
- .getChild('database')
338
- ?.get(0);
339
- }));
340
- const newTables = await get().db.loadTableSchemas();
341
- // Only update if there's an actual change in the schemas
342
- if (!deepEquals(newTables, get().db.tables)) {
306
+ refreshPromise = (async () => {
307
+ try {
308
+ let newTables;
309
+ do {
310
+ pendingSchemaRefresh = false;
311
+ const connector = await get().db.getConnector();
312
+ const result = await connector.query(`SELECT current_schema() AS schema, current_database() AS database`);
313
+ set((state) => produce(state, (draft) => {
314
+ draft.db.currentSchema = result
315
+ .getChild('schema')
316
+ ?.get(0);
317
+ draft.db.currentDatabase = result
318
+ .getChild('database')
319
+ ?.get(0);
320
+ }));
321
+ newTables = await get().db.loadTableSchemas();
322
+ } while (pendingSchemaRefresh);
323
+ if (!deepEquals(newTables, get().db.tables)) {
324
+ set((state) => produce(state, (draft) => {
325
+ draft.db.tables = newTables;
326
+ draft.db.schemaTrees = createDbSchemaTrees(newTables);
327
+ }));
328
+ }
329
+ return newTables;
330
+ }
331
+ catch (err) {
332
+ get().room.captureException(err);
333
+ return [];
334
+ }
335
+ finally {
336
+ refreshPromise = null;
343
337
  set((state) => produce(state, (draft) => {
344
- draft.db.tables = newTables;
345
- draft.db.schemaTrees = createDbSchemaTrees(newTables);
338
+ draft.db.isRefreshingTableSchemas = false;
346
339
  }));
347
340
  }
348
- return newTables;
349
- }
350
- catch (err) {
351
- get().room.captureException(err);
352
- return [];
353
- }
354
- finally {
355
- set((state) => produce(state, (draft) => {
356
- draft.db.isRefreshingTableSchemas = false;
357
- }));
358
- }
341
+ })();
342
+ return refreshPromise;
359
343
  },
360
344
  async sqlSelectToJson(sql) {
361
345
  const connector = await get().db.getConnector();
@@ -415,10 +399,18 @@ function createDuckDbCommands() {
415
399
  },
416
400
  {
417
401
  id: 'db.drop-table',
418
- name: 'Drop table',
419
- description: 'Drop a table from DuckDB by name',
402
+ name: 'Drop relation',
403
+ description: 'Drop a table or view from DuckDB by name',
420
404
  group: 'Database',
421
- keywords: ['duckdb', 'database', 'drop', 'table', 'delete'],
405
+ keywords: [
406
+ 'duckdb',
407
+ 'database',
408
+ 'drop',
409
+ 'table',
410
+ 'view',
411
+ 'relation',
412
+ 'delete',
413
+ ],
422
414
  inputSchema: DropTableCommandInput,
423
415
  inputDescription: 'Provide a tableName to remove from DuckDB.',
424
416
  metadata: {
@@ -436,11 +428,11 @@ function createDuckDbCommands() {
436
428
  },
437
429
  execute: async ({ getState }, input) => {
438
430
  const { tableName } = input;
439
- await getState().db.dropTable(tableName);
431
+ await getState().db.dropRelation(tableName);
440
432
  return {
441
433
  success: true,
442
434
  commandId: 'db.drop-table',
443
- message: `Dropped table "${tableName}".`,
435
+ message: `Dropped relation "${tableName}".`,
444
436
  };
445
437
  },
446
438
  },