@sqlrooms/duckdb 0.15.0 → 0.16.1

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.
Files changed (40) hide show
  1. package/dist/DuckDbSlice.d.ts +102 -23
  2. package/dist/DuckDbSlice.d.ts.map +1 -1
  3. package/dist/DuckDbSlice.js +201 -63
  4. package/dist/DuckDbSlice.js.map +1 -1
  5. package/dist/connectors/BaseDuckDbConnector.d.ts +22 -5
  6. package/dist/connectors/BaseDuckDbConnector.d.ts.map +1 -1
  7. package/dist/connectors/BaseDuckDbConnector.js +66 -15
  8. package/dist/connectors/BaseDuckDbConnector.js.map +1 -1
  9. package/dist/connectors/DuckDbConnector.d.ts +237 -4
  10. package/dist/connectors/DuckDbConnector.d.ts.map +1 -1
  11. package/dist/connectors/DuckDbConnector.js.map +1 -1
  12. package/dist/connectors/WasmDuckDbConnector.d.ts +2 -1
  13. package/dist/connectors/WasmDuckDbConnector.d.ts.map +1 -1
  14. package/dist/connectors/WasmDuckDbConnector.js +90 -17
  15. package/dist/connectors/WasmDuckDbConnector.js.map +1 -1
  16. package/dist/duckdb-utils.d.ts +67 -0
  17. package/dist/duckdb-utils.d.ts.map +1 -1
  18. package/dist/duckdb-utils.js +195 -0
  19. package/dist/duckdb-utils.js.map +1 -1
  20. package/dist/exportToCsv.js +1 -1
  21. package/dist/exportToCsv.js.map +1 -1
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/schemaTree.d.ts +8 -0
  27. package/dist/schemaTree.d.ts.map +1 -0
  28. package/dist/schemaTree.js +76 -0
  29. package/dist/schemaTree.js.map +1 -0
  30. package/dist/typeCategories.d.ts +16 -0
  31. package/dist/typeCategories.d.ts.map +1 -0
  32. package/dist/typeCategories.js +72 -0
  33. package/dist/typeCategories.js.map +1 -0
  34. package/dist/types.d.ts +35 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js.map +1 -1
  37. package/dist/useSql.d.ts.map +1 -1
  38. package/dist/useSql.js +11 -7
  39. package/dist/useSql.js.map +1 -1
  40. package/package.json +13 -12
@@ -2,10 +2,15 @@ import { ProjectState } from '@sqlrooms/project';
2
2
  import * as arrow from 'apache-arrow';
3
3
  import { z } from 'zod';
4
4
  import { StateCreator } from 'zustand';
5
- import { DuckDbConnector } from './connectors/DuckDbConnector';
6
- import { DataTable } from './types';
5
+ import { DuckDbConnector, QueryHandle } from './connectors/DuckDbConnector';
6
+ import { QualifiedTableName } from './duckdb-utils';
7
+ import { DataTable, DbSchemaNode } from './types';
7
8
  export declare const DuckDbSliceConfig: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
8
9
  export type DuckDbSliceConfig = z.infer<typeof DuckDbSliceConfig>;
10
+ export type SchemaAndDatabase = {
11
+ schema?: string;
12
+ database?: string;
13
+ };
9
14
  export declare function createDefaultDuckDbConfig(): DuckDbSliceConfig;
10
15
  /**
11
16
  * State and actions for the DuckDB slice
@@ -16,11 +21,36 @@ export type DuckDbSliceState = {
16
21
  * The DuckDB connector instance
17
22
  */
18
23
  connector: DuckDbConnector;
24
+ /**
25
+ * @deprecated We shouldn't limit the schema to a single one.
26
+ */
19
27
  schema: string;
28
+ currentSchema: string | undefined;
29
+ currentDatabase: string | undefined;
30
+ /**
31
+ * Cache of refreshed table schemas
32
+ */
20
33
  tables: DataTable[];
34
+ /**
35
+ * Cache of row counts for tables
36
+ */
21
37
  tableRowCounts: {
22
38
  [tableName: string]: number;
23
39
  };
40
+ /**
41
+ * Cache of schema trees for tables
42
+ */
43
+ schemaTrees?: DbSchemaNode[];
44
+ /**
45
+ * Cache of currently running query handles
46
+ */
47
+ queryCache: {
48
+ [key: string]: QueryHandle;
49
+ };
50
+ /**
51
+ * Whether the table schemas are being refreshed
52
+ */
53
+ isRefreshingTableSchemas: boolean;
24
54
  /**
25
55
  * Set a new DuckDB connector
26
56
  */
@@ -39,10 +69,29 @@ export type DuckDbSliceState = {
39
69
  * @param data - The data to add to the table: an arrow table or an array of records.
40
70
  * @returns A promise that resolves to the table that was added.
41
71
  */
42
- addTable(tableName: string, data: arrow.Table | Record<string, unknown>[]): Promise<DataTable>;
72
+ addTable(tableName: string | QualifiedTableName, data: arrow.Table | Record<string, unknown>[]): Promise<DataTable>;
73
+ /**
74
+ * Load the schemas of the tables in the database.
75
+ */
76
+ loadTableSchemas(filter?: SchemaAndDatabase & {
77
+ table?: string;
78
+ }): Promise<DataTable[]>;
79
+ /**
80
+ * @deprecated Use findTableByName instead
81
+ */
43
82
  getTable(tableName: string): DataTable | undefined;
44
- setTableRowCount(tableName: string, rowCount: number): void;
45
- findTableByName(tableName: string): DataTable | undefined;
83
+ /**
84
+ * @internal Avoid using this directly, it's for internal use.
85
+ */
86
+ setTableRowCount(tableName: string | QualifiedTableName, rowCount: number): void;
87
+ /**
88
+ * Find a table by name in the last refreshed table schemas.
89
+ * If no schema or database is provided, the table will be found in the current schema
90
+ * and database (from last table schemas refresh).
91
+ * @param tableName - The name of the table to find or a qualified table name.
92
+ * @returns The table or undefined if not found.
93
+ */
94
+ findTableByName(tableName: string | QualifiedTableName): DataTable | undefined;
46
95
  /**
47
96
  * Refresh table schemas from the database.
48
97
  * @returns A promise that resolves to the updated tables.
@@ -53,45 +102,75 @@ export type DuckDbSliceState = {
53
102
  */
54
103
  getConnector: () => Promise<DuckDbConnector>;
55
104
  /**
56
- * Get the tables in the database
57
- *
58
- * @param schema - The schema to get the tables from. Defaults to 'main'. Pass '*' to get all tables.
59
- * @returns The tables in the database.
105
+ * @deprecated Use .loadTableRowCount() instead
60
106
  */
61
- getTables: (schema?: string) => Promise<string[]>;
107
+ getTableRowCount: (table: string, schema?: string) => Promise<number>;
62
108
  /**
63
- * Get the row count of a table
109
+ * Load the row count of a table
64
110
  */
65
- getTableRowCount: (tableName: string, schema?: string) => Promise<number>;
111
+ loadTableRowCount: (tableName: string | QualifiedTableName) => Promise<number>;
66
112
  /**
67
- * Get the schema of a table
113
+ * Execute a query with query handle (not result) caching and deduplication
114
+ * @param query - The SQL query to execute
115
+ * @returns The QueryHandle for the query or null if disabled
68
116
  */
69
- getTableSchema: (tableName: string, schema?: string) => Promise<DataTable>;
117
+ executeSql: (query: string) => Promise<QueryHandle | null>;
70
118
  /**
71
- * Get the schemas of all tables in the database.
72
- *
73
- * @param schema - The schema to get the tables from. Defaults to 'main'. Pass '*' to get all schemas.
74
- * @returns The schemas of all tables in the database.
119
+ * @deprecated Use .tables or .loadTableSchemas() instead
120
+ */
121
+ getTables: (schema?: string) => Promise<string[]>;
122
+ /**
123
+ * @deprecated Use .loadTableSchemas() instead
124
+ */
125
+ getTableSchema: (tableName: string, schema?: string) => Promise<DataTable | undefined>;
126
+ /**
127
+ * @deprecated Use .tables or .loadTableSchemas() instead
75
128
  */
76
129
  getTableSchemas: (schema?: string) => Promise<DataTable[]>;
77
130
  /**
78
131
  * Check if a table exists
79
132
  */
80
- checkTableExists: (tableName: string, schema?: string) => Promise<boolean>;
133
+ checkTableExists: (tableName: string | QualifiedTableName) => Promise<boolean>;
81
134
  /**
82
- * Drop a table
135
+ * Delete a table with optional schema and database
136
+ * @param tableName - The name of the table to delete
137
+ * @param options - Optional parameters including schema and database
83
138
  */
84
- dropTable: (tableName: string) => Promise<void>;
139
+ dropTable: (tableName: string | QualifiedTableName) => Promise<void>;
85
140
  /**
86
141
  * Create a table from a query.
87
142
  * @param tableName - The name of the table to create.
88
143
  * @param query - The query to create the table from.
89
144
  * @returns The table that was created.
90
145
  */
91
- createTableFromQuery: (tableName: string, query: string) => Promise<{
92
- tableName: string;
146
+ createTableFromQuery: (tableName: string | QualifiedTableName, query: string) => Promise<{
147
+ tableName: string | QualifiedTableName;
93
148
  rowCount: number;
94
149
  }>;
150
+ /**
151
+ * Parse a SQL SELECT statement to JSON
152
+ * @param sql - The SQL SELECT statement to parse.
153
+ * @returns A promise that resolves to the parsed JSON.
154
+ */
155
+ sqlSelectToJson: (sql: string) => Promise<{
156
+ error: true;
157
+ error_type: string;
158
+ error_message: string;
159
+ error_subtype: string;
160
+ position: string;
161
+ } | {
162
+ error: false;
163
+ statements: {
164
+ node: {
165
+ from_table: {
166
+ alias: string;
167
+ show_type: string;
168
+ table_name: string;
169
+ };
170
+ select_list: Record<string, unknown>[];
171
+ };
172
+ }[];
173
+ }>;
95
174
  };
96
175
  };
97
176
  /**
@@ -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":"AAAA,OAAO,EAEL,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AACrC,OAAO,EAAC,eAAe,EAAE,WAAW,EAAC,MAAM,8BAA8B,CAAC;AAE1E,OAAO,EAML,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAC,SAAS,EAAe,YAAY,EAAC,MAAM,SAAS,CAAC;AAE7D,eAAO,MAAM,iBAAiB,gDAE5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,yBAAyB,IAAI,iBAAiB,CAI7D;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE;QACF;;WAEG;QACH,SAAS,EAAE,eAAe,CAAC;QAC3B;;WAEG;QACH,MAAM,EAAE,MAAM,CAAC;QAEf,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;QAClC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;QAEpC;;WAEG;QACH,MAAM,EAAE,SAAS,EAAE,CAAC;QACpB;;WAEG;QACH,cAAc,EAAE;YAAC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;SAAC,CAAC;QAC9C;;WAEG;QACH,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;QAC7B;;WAEG;QACH,UAAU,EAAE;YAAC,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;SAAC,CAAC;QACzC;;WAEG;QACH,wBAAwB,EAAE,OAAO,CAAC;QAElC;;WAEG;QACH,YAAY,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;QAEnD;;WAEG;QACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAEhC;;WAEG;QACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAE7B;;;;;WAKG;QACH,QAAQ,CACN,SAAS,EAAE,MAAM,GAAG,kBAAkB,EACtC,IAAI,EAAE,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC5C,OAAO,CAAC,SAAS,CAAC,CAAC;QAEtB;;WAEG;QACH,gBAAgB,CACd,MAAM,CAAC,EAAE,iBAAiB,GAAG;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAC,GAC5C,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QAExB;;WAEG;QACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;QAEnD;;WAEG;QACH,gBAAgB,CACd,SAAS,EAAE,MAAM,GAAG,kBAAkB,EACtC,QAAQ,EAAE,MAAM,GACf,IAAI,CAAC;QAER;;;;;;WAMG;QACH,eAAe,CACb,SAAS,EAAE,MAAM,GAAG,kBAAkB,GACrC,SAAS,GAAG,SAAS,CAAC;QAEzB;;;WAGG;QACH,mBAAmB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QAC5C;;WAEG;QACH,YAAY,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;QAE7C;;WAEG;QACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAEtE;;WAEG;QACH,iBAAiB,EAAE,CACjB,SAAS,EAAE,MAAM,GAAG,kBAAkB,KACnC,OAAO,CAAC,MAAM,CAAC,CAAC;QAErB;;;;WAIG;QACH,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QAE3D;;WAEG;QACH,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAElD;;WAEG;QACH,cAAc,EAAE,CACd,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,KACZ,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;QAEpC;;WAEG;QACH,eAAe,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QAE3D;;WAEG;QACH,gBAAgB,EAAE,CAChB,SAAS,EAAE,MAAM,GAAG,kBAAkB,KACnC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEtB;;;;WAIG;QACH,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAErE;;;;;WAKG;QACH,oBAAoB,EAAE,CACpB,SAAS,EAAE,MAAM,GAAG,kBAAkB,EACtC,KAAK,EAAE,MAAM,KACV,OAAO,CAAC;YAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAC,CAAC,CAAC;QAEzE;;;;WAIG;QACH,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CACrC;YACE,KAAK,EAAE,IAAI,CAAC;YACZ,UAAU,EAAE,MAAM,CAAC;YACnB,aAAa,EAAE,MAAM,CAAC;YACtB,aAAa,EAAE,MAAM,CAAC;YACtB,QAAQ,EAAE,MAAM,CAAC;SAClB,GACD;YACE,KAAK,EAAE,KAAK,CAAC;YACb,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,UAAU,EAAE;wBACV,KAAK,EAAE,MAAM,CAAC;wBACd,SAAS,EAAE,MAAM,CAAC;wBAClB,UAAU,EAAE,MAAM,CAAC;qBACpB,CAAC;oBACF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;iBACxC,CAAC;aACH,EAAE,CAAC;SACL,CACJ,CAAC;KACH,CAAC;CACH,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAqC,GACtC,EAAE;IACD,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B,GAAG,YAAY,CAAC,gBAAgB,CAAC,CA8UjC;AAED,KAAK,sBAAsB,GAAG,YAAY,CAAC,iBAAiB,CAAC,GAC3D,gBAAgB,CAAC;AAEnB,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,CAAC,GAC7C,CAAC,CAMH"}
@@ -1,10 +1,11 @@
1
1
  import { createBaseSlice, useBaseProjectStore, } from '@sqlrooms/project';
2
2
  import * as arrow from 'apache-arrow';
3
- import { produce } from 'immer';
4
3
  import deepEquals from 'fast-deep-equal';
4
+ import { produce } from 'immer';
5
5
  import { z } from 'zod';
6
6
  import { WasmDuckDbConnector } from './connectors/WasmDuckDbConnector';
7
- import { getColValAsNumber } from './duckdb-utils';
7
+ import { escapeId, escapeVal, getColValAsNumber, isQualifiedTableName, makeQualifiedTableName, splitSqlStatements, } from './duckdb-utils';
8
+ import { createDbSchemaTrees as createDbSchemaTrees } from './schemaTree';
8
9
  export const DuckDbSliceConfig = z.object({
9
10
  // nothing yet
10
11
  });
@@ -21,9 +22,14 @@ export function createDuckDbSlice({ connector = new WasmDuckDbConnector(), }) {
21
22
  return {
22
23
  db: {
23
24
  connector, // Will be initialized during init
24
- schema: 'main',
25
+ schema: 'main', // TODO: remove schema, we should not limit the schema to a single one.
26
+ currentSchema: undefined,
27
+ currentDatabase: undefined,
28
+ isRefreshingTableSchemas: false,
25
29
  tables: [],
26
30
  tableRowCounts: {},
31
+ schemaTree: undefined,
32
+ queryCache: {},
27
33
  setConnector: (connector) => {
28
34
  set(produce((state) => {
29
35
  state.config.dataSources = [];
@@ -32,9 +38,9 @@ export function createDuckDbSlice({ connector = new WasmDuckDbConnector(), }) {
32
38
  },
33
39
  initialize: async () => {
34
40
  await get().db.connector.initialize();
41
+ await get().db.refreshTableSchemas();
35
42
  },
36
43
  getConnector: async () => {
37
- await get().db.initialize();
38
44
  return get().db.connector;
39
45
  },
40
46
  destroy: async () => {
@@ -48,99 +54,231 @@ export function createDuckDbSlice({ connector = new WasmDuckDbConnector(), }) {
48
54
  }
49
55
  },
50
56
  async createTableFromQuery(tableName, query) {
57
+ const qualifiedName = isQualifiedTableName(tableName)
58
+ ? tableName
59
+ : makeQualifiedTableName({ table: tableName });
51
60
  const connector = await get().db.getConnector();
52
- const rowCount = getColValAsNumber(await connector.query(`CREATE OR REPLACE TABLE main.${tableName} AS (
53
- ${query}
54
- )`));
61
+ const statements = splitSqlStatements(query);
62
+ if (statements.length !== 1) {
63
+ throw new Error('Query must contain exactly one statement');
64
+ }
65
+ const statement = statements[0];
66
+ const parsedQuery = await get().db.sqlSelectToJson(statement);
67
+ if (parsedQuery.error) {
68
+ throw new Error('Query is not a valid SELECT statement');
69
+ }
70
+ const rowCount = getColValAsNumber(await connector.query(`CREATE OR REPLACE TABLE ${qualifiedName} AS (
71
+ ${statements[0]}
72
+ )`).result);
55
73
  return { tableName, rowCount };
56
74
  },
57
- async getTables(schema = 'main') {
58
- const connector = await get().db.getConnector();
59
- const tablesResults = await connector.query(`SELECT * FROM information_schema.tables
60
- ${schema === '*' ? '' : `WHERE table_schema = '${schema}'`}
61
- ORDER BY table_name`);
62
- const tableNames = [];
63
- for (let i = 0; i < tablesResults.numRows; i++) {
64
- tableNames.push(tablesResults.getChild('table_name')?.get(i));
65
- }
66
- return tableNames;
75
+ /**
76
+ * @deprecated Use .tables or .loadTableSchemas() instead
77
+ */
78
+ async getTables(schema) {
79
+ const tableSchemas = await get().db.loadTableSchemas({ schema });
80
+ return tableSchemas.map((t) => t.table.table);
67
81
  },
82
+ /**
83
+ * @deprecated Use .loadTableSchemas() instead
84
+ */
68
85
  async getTableSchema(tableName, schema = 'main') {
69
- const connector = await get().db.getConnector();
70
- const describeResults = await connector.query(`DESCRIBE ${schema}.${tableName}`);
71
- const columnNames = describeResults.getChild('column_name');
72
- const columnTypes = describeResults.getChild('column_type');
73
- const columns = [];
74
- for (let di = 0; di < describeResults.numRows; di++) {
75
- const columnName = columnNames?.get(di);
76
- const columnType = columnTypes?.get(di);
77
- columns.push({ name: columnName, type: columnType });
78
- }
79
- return {
80
- tableName,
81
- columns,
82
- };
86
+ const newLocal = await get().db.loadTableSchemas({
87
+ schema,
88
+ table: tableName,
89
+ });
90
+ return newLocal[0];
91
+ },
92
+ /**
93
+ * @deprecated Use .loadTableRowCount() instead
94
+ */
95
+ async getTableRowCount(table, schema = 'main') {
96
+ return get().db.loadTableRowCount({ table, schema });
83
97
  },
84
- async getTableRowCount(tableName, schema = 'main') {
98
+ async loadTableRowCount(tableName) {
99
+ const { schema, database, table } = typeof tableName === 'string'
100
+ ? { table: tableName }
101
+ : tableName || {};
85
102
  const connector = await get().db.getConnector();
86
- const result = await connector.query(`SELECT COUNT(*) FROM ${schema}.${tableName}`);
103
+ const result = await connector.query(`SELECT COUNT(*) FROM ${makeQualifiedTableName({
104
+ schema,
105
+ database,
106
+ table,
107
+ })}`).result;
87
108
  return getColValAsNumber(result);
88
109
  },
89
- async getTableSchemas(schema = 'main') {
90
- const tableNames = await get().db.getTables(schema);
91
- const tablesInfo = [];
92
- for (const tableName of tableNames) {
93
- tablesInfo.push(await get().db.getTableSchema(tableName, schema));
110
+ /**
111
+ * @deprecated Use .loadTableSchemas() instead
112
+ */
113
+ async getTableSchemas(schema) {
114
+ return await get().db.loadTableSchemas({ schema });
115
+ },
116
+ async loadTableSchemas(filter) {
117
+ const { schema, database, table } = filter || {};
118
+ const describeResults = await connector.query(`FROM (DESCRIBE)
119
+ SELECT
120
+ database, schema,
121
+ name, column_names, column_types
122
+ ${schema || database || table
123
+ ? `WHERE ${[
124
+ schema ? `schema = '${escapeId(schema)}'` : '',
125
+ database ? `database = '${escapeId(database)}'` : '',
126
+ table ? `name = '${escapeId(table)}'` : '',
127
+ ]
128
+ .filter(Boolean)
129
+ .join(' AND ')}`
130
+ : ''}`).result;
131
+ const newTables = [];
132
+ for (let i = 0; i < describeResults.numRows; i++) {
133
+ const database = describeResults.getChild('database')?.get(i);
134
+ const schema = describeResults.getChild('schema')?.get(i);
135
+ const table = describeResults.getChild('name')?.get(i);
136
+ const columnNames = describeResults
137
+ .getChild('column_names')
138
+ ?.get(i);
139
+ const columnTypes = describeResults
140
+ .getChild('column_types')
141
+ ?.get(i);
142
+ const columns = [];
143
+ for (let di = 0; di < columnNames.length; di++) {
144
+ columns.push({
145
+ name: columnNames.get(di),
146
+ type: columnTypes.get(di),
147
+ });
148
+ }
149
+ newTables.push({
150
+ table: makeQualifiedTableName({ database, schema, table }),
151
+ database,
152
+ schema,
153
+ tableName: table,
154
+ columns,
155
+ });
94
156
  }
95
- return tablesInfo;
157
+ return newTables;
96
158
  },
97
- async checkTableExists(tableName, schema = 'main') {
98
- const connector = await get().db.getConnector();
99
- const res = await connector.query(`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${schema}' AND table_name = '${tableName}'`);
100
- return getColValAsNumber(res) > 0;
159
+ async checkTableExists(tableName) {
160
+ const qualifiedName = isQualifiedTableName(tableName)
161
+ ? tableName
162
+ : makeQualifiedTableName({ table: tableName });
163
+ const table = (await get().db.loadTableSchemas(qualifiedName))[0];
164
+ if (!table) {
165
+ return false;
166
+ }
167
+ return true;
101
168
  },
102
169
  async dropTable(tableName) {
103
170
  const connector = await get().db.getConnector();
104
- await connector.query(`DROP TABLE IF EXISTS ${tableName};`);
171
+ const qualifiedTable = isQualifiedTableName(tableName)
172
+ ? tableName
173
+ : makeQualifiedTableName({ table: tableName });
174
+ await connector.query(`DROP TABLE IF EXISTS ${qualifiedTable};`)
175
+ .result;
176
+ await get().db.refreshTableSchemas();
105
177
  },
106
178
  async addTable(tableName, data) {
107
- const { tables } = get().db;
108
- const table = tables.find((t) => t.tableName === tableName);
109
- if (table) {
110
- return table;
111
- }
179
+ const qualifiedName = isQualifiedTableName(tableName)
180
+ ? tableName
181
+ : makeQualifiedTableName({ table: tableName });
112
182
  const { db } = get();
113
183
  if (data instanceof arrow.Table) {
114
- await db.connector.loadArrow(data, tableName);
184
+ // TODO: make sure the table is replaced
185
+ await db.connector.loadArrow(data, qualifiedName.toString());
115
186
  }
116
187
  else {
117
- await db.connector.loadObjects(data, tableName);
188
+ await db.connector.loadObjects(data, qualifiedName.toString(), {
189
+ replace: true,
190
+ });
191
+ }
192
+ const newTable = (await db.loadTableSchemas(qualifiedName))[0];
193
+ if (!newTable) {
194
+ throw new Error('Failed to add table');
118
195
  }
119
- const newTable = await db.getTableSchema(tableName);
120
196
  set((state) => produce(state, (draft) => {
121
197
  draft.db.tables.push(newTable);
122
198
  }));
199
+ await get().db.refreshTableSchemas();
123
200
  return newTable;
124
201
  },
125
- setTableRowCount: (tableName, rowCount) => set((state) => produce(state, (draft) => {
126
- draft.db.tableRowCounts[tableName] = rowCount;
127
- })),
202
+ async setTableRowCount(tableName, rowCount) {
203
+ const qualifiedName = isQualifiedTableName(tableName)
204
+ ? tableName
205
+ : makeQualifiedTableName({ table: tableName });
206
+ set((state) => produce(state, (draft) => {
207
+ draft.db.tableRowCounts[qualifiedName.toString()] = rowCount;
208
+ }));
209
+ },
128
210
  getTable(tableName) {
129
- return get().db.tables.find((t) => t.tableName === tableName);
211
+ return get().db.findTableByName(tableName);
130
212
  },
131
213
  findTableByName(tableName) {
132
- return get().db.tables.find((t) => t.tableName === tableName);
214
+ const { table, schema, database } = {
215
+ schema: get().db.currentSchema,
216
+ database: get().db.currentDatabase,
217
+ ...(typeof tableName === 'string' ? { table: tableName } : tableName),
218
+ };
219
+ return get().db.tables.find((t) => t.table.table === table &&
220
+ (!schema || t.table.schema === schema) &&
221
+ (!database || t.table.database === database));
133
222
  },
134
223
  async refreshTableSchemas() {
135
- const newTables = await get().db.getTableSchemas();
136
- const currentTables = get().db.tables;
137
- // Only update if there's an actual change in the schemas
138
- if (!deepEquals(newTables, currentTables)) {
224
+ set((state) => produce(state, (draft) => {
225
+ draft.db.isRefreshingTableSchemas = true;
226
+ }));
227
+ try {
228
+ const connector = await get().db.getConnector();
229
+ const result = await connector.query(`SELECT current_schema() AS schema, current_database() AS database`).result;
139
230
  set((state) => produce(state, (draft) => {
140
- draft.db.tables = newTables;
231
+ draft.db.currentSchema = result.getChild('schema')?.get(0);
232
+ draft.db.currentDatabase = result.getChild('database')?.get(0);
141
233
  }));
234
+ const newTables = await get().db.loadTableSchemas();
235
+ // Only update if there's an actual change in the schemas
236
+ if (!deepEquals(newTables, get().db.tables)) {
237
+ set((state) => produce(state, (draft) => {
238
+ draft.db.tables = newTables;
239
+ draft.db.schemaTrees = createDbSchemaTrees(newTables);
240
+ }));
241
+ }
242
+ return newTables;
142
243
  }
143
- return newTables;
244
+ catch (err) {
245
+ get().project.captureException(err);
246
+ return [];
247
+ }
248
+ finally {
249
+ set((state) => produce(state, (draft) => {
250
+ draft.db.isRefreshingTableSchemas = false;
251
+ }));
252
+ }
253
+ },
254
+ async sqlSelectToJson(sql) {
255
+ const connector = await get().db.getConnector();
256
+ const parsedQuery = (await connector.query(`SELECT json_serialize_sql(${escapeVal(sql)})`).result)
257
+ .getChildAt(0)
258
+ ?.get(0);
259
+ return JSON.parse(parsedQuery);
260
+ },
261
+ async executeSql(query) {
262
+ // Create a unique key for this query
263
+ const queryKey = `${query}`;
264
+ const connector = await get().db.getConnector();
265
+ // Check if we already have a cached query for this key
266
+ const existingQuery = get().db.queryCache[queryKey];
267
+ if (existingQuery) {
268
+ return existingQuery;
269
+ }
270
+ const queryHandle = connector.query(query);
271
+ // Cache the query handle immediately
272
+ set((state) => produce(state, (draft) => {
273
+ draft.db.queryCache[queryKey] = queryHandle;
274
+ }));
275
+ queryHandle.result.finally(() => {
276
+ // remove from cache after completion
277
+ set((state) => produce(state, (draft) => {
278
+ delete draft.db.queryCache[queryKey];
279
+ }));
280
+ });
281
+ return queryHandle;
144
282
  },
145
283
  },
146
284
  };
@@ -1 +1 @@
1
- {"version":3,"file":"DuckDbSlice.js","sourceRoot":"","sources":["../src/DuckDbSlice.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createBaseSlice,\n ProjectState,\n useBaseProjectStore,\n} from '@sqlrooms/project';\nimport * as arrow from 'apache-arrow';\nimport {produce} from 'immer';\nimport deepEquals from 'fast-deep-equal';\nimport {z} from 'zod';\nimport {StateCreator} from 'zustand';\nimport {DuckDbConnector} from './connectors/DuckDbConnector';\nimport {WasmDuckDbConnector} from './connectors/WasmDuckDbConnector';\nimport {getColValAsNumber} from './duckdb-utils';\nimport {DataTable, TableColumn} from './types';\n\nexport const DuckDbSliceConfig = z.object({\n // nothing yet\n});\nexport type DuckDbSliceConfig = z.infer<typeof DuckDbSliceConfig>;\n\nexport function createDefaultDuckDbConfig(): DuckDbSliceConfig {\n return {\n // nothing yet\n };\n}\n\n/**\n * State and actions for the DuckDB slice\n */\nexport type DuckDbSliceState = {\n db: {\n /**\n * The DuckDB connector instance\n */\n connector: DuckDbConnector;\n schema: string;\n\n tables: DataTable[];\n tableRowCounts: {[tableName: string]: number};\n\n /**\n * Set a new DuckDB connector\n */\n setConnector: (connector: DuckDbConnector) => void;\n\n /**\n * Initialize the connector (creates a WasmDuckDbConnector if none exists)\n */\n initialize: () => Promise<void>;\n\n /**\n * Close and clean up the connector\n */\n destroy: () => Promise<void>;\n\n /**\n * Add a table to the project.\n * @param tableName - The name of the table to add.\n * @param data - The data to add to the table: an arrow table or an array of records.\n * @returns A promise that resolves to the table that was added.\n */\n addTable(\n tableName: string,\n data: arrow.Table | Record<string, unknown>[],\n ): Promise<DataTable>;\n getTable(tableName: string): DataTable | undefined;\n setTableRowCount(tableName: string, rowCount: number): void;\n findTableByName(tableName: string): DataTable | undefined;\n /**\n * Refresh table schemas from the database.\n * @returns A promise that resolves to the updated tables.\n */\n refreshTableSchemas(): Promise<DataTable[]>;\n /**\n * Get the connector. If it's not initialized, it will be initialized.\n */\n getConnector: () => Promise<DuckDbConnector>;\n\n /**\n * Get the tables in the database\n *\n * @param schema - The schema to get the tables from. Defaults to 'main'. Pass '*' to get all tables.\n * @returns The tables in the database.\n */\n getTables: (schema?: string) => Promise<string[]>;\n\n /**\n * Get the row count of a table\n */\n getTableRowCount: (tableName: string, schema?: string) => Promise<number>;\n\n /**\n * Get the schema of a table\n */\n getTableSchema: (tableName: string, schema?: string) => Promise<DataTable>;\n\n /**\n * Get the schemas of all tables in the database.\n *\n * @param schema - The schema to get the tables from. Defaults to 'main'. Pass '*' to get all schemas.\n * @returns The schemas of all tables in the database.\n */\n getTableSchemas: (schema?: string) => Promise<DataTable[]>;\n\n /**\n * Check if a table exists\n */\n checkTableExists: (tableName: string, schema?: string) => Promise<boolean>;\n\n /**\n * Drop a table\n */\n dropTable: (tableName: string) => Promise<void>;\n\n /**\n * Create a table from a query.\n * @param tableName - The name of the table to create.\n * @param query - The query to create the table from.\n * @returns The table that was created.\n */\n createTableFromQuery: (\n tableName: string,\n query: string,\n ) => Promise<{tableName: string; rowCount: number}>;\n };\n};\n\n/**\n * Create a DuckDB slice for managing the connector\n */\nexport function createDuckDbSlice({\n connector = new WasmDuckDbConnector(),\n}: {\n connector?: DuckDbConnector;\n}): StateCreator<DuckDbSliceState> {\n return createBaseSlice<DuckDbSliceConfig, DuckDbSliceState>((set, get) => {\n return {\n db: {\n connector, // Will be initialized during init\n schema: 'main',\n tables: [],\n tableRowCounts: {},\n\n setConnector: (connector: DuckDbConnector) => {\n set(\n produce((state) => {\n state.config.dataSources = [];\n state.db.connector = connector;\n }),\n );\n },\n\n initialize: async () => {\n await get().db.connector.initialize();\n },\n\n getConnector: async () => {\n await get().db.initialize();\n return get().db.connector;\n },\n\n destroy: async () => {\n try {\n if (get().db.connector) {\n await get().db.connector.destroy();\n }\n } catch (err) {\n console.error('Error during DuckDB shutdown:', err);\n }\n },\n\n async createTableFromQuery(tableName: string, query: string) {\n const connector = await get().db.getConnector();\n const rowCount = getColValAsNumber(\n await connector.query(\n `CREATE OR REPLACE TABLE main.${tableName} AS (\n ${query}\n )`,\n ),\n );\n return {tableName, rowCount};\n },\n\n async getTables(schema = 'main'): Promise<string[]> {\n const connector = await get().db.getConnector();\n const tablesResults = await connector.query(\n `SELECT * FROM information_schema.tables \n ${schema === '*' ? '' : `WHERE table_schema = '${schema}'`}\n ORDER BY table_name`,\n );\n const tableNames: string[] = [];\n for (let i = 0; i < tablesResults.numRows; i++) {\n tableNames.push(tablesResults.getChild('table_name')?.get(i));\n }\n return tableNames;\n },\n\n async getTableSchema(\n tableName: string,\n schema = 'main',\n ): Promise<DataTable> {\n const connector = await get().db.getConnector();\n const describeResults = await connector.query(\n `DESCRIBE ${schema}.${tableName}`,\n );\n const columnNames = describeResults.getChild('column_name');\n const columnTypes = describeResults.getChild('column_type');\n const columns: TableColumn[] = [];\n for (let di = 0; di < describeResults.numRows; di++) {\n const columnName = columnNames?.get(di);\n const columnType = columnTypes?.get(di);\n columns.push({name: columnName, type: columnType});\n }\n return {\n tableName,\n columns,\n };\n },\n\n async getTableRowCount(\n tableName: string,\n schema = 'main',\n ): Promise<number> {\n const connector = await get().db.getConnector();\n const result = await connector.query(\n `SELECT COUNT(*) FROM ${schema}.${tableName}`,\n );\n return getColValAsNumber(result);\n },\n\n async getTableSchemas(schema = 'main'): Promise<DataTable[]> {\n const tableNames = await get().db.getTables(schema);\n const tablesInfo: DataTable[] = [];\n for (const tableName of tableNames) {\n tablesInfo.push(await get().db.getTableSchema(tableName, schema));\n }\n return tablesInfo;\n },\n\n async checkTableExists(\n tableName: string,\n schema = 'main',\n ): Promise<boolean> {\n const connector = await get().db.getConnector();\n const res = await connector.query(\n `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${schema}' AND table_name = '${tableName}'`,\n );\n return getColValAsNumber(res) > 0;\n },\n\n async dropTable(tableName: string): Promise<void> {\n const connector = await get().db.getConnector();\n await connector.query(`DROP TABLE IF EXISTS ${tableName};`);\n },\n\n async addTable(tableName, data) {\n const {tables} = get().db;\n const table = tables.find((t) => t.tableName === tableName);\n if (table) {\n return table;\n }\n\n const {db} = get();\n if (data instanceof arrow.Table) {\n await db.connector.loadArrow(data, tableName);\n } else {\n await db.connector.loadObjects(data, tableName);\n }\n const newTable = await db.getTableSchema(tableName);\n\n set((state) =>\n produce(state, (draft) => {\n draft.db.tables.push(newTable);\n }),\n );\n return newTable;\n },\n\n setTableRowCount: (tableName, rowCount) =>\n set((state) =>\n produce(state, (draft) => {\n draft.db.tableRowCounts[tableName] = rowCount;\n }),\n ),\n\n getTable(tableName) {\n return get().db.tables.find((t) => t.tableName === tableName);\n },\n\n findTableByName(tableName: string) {\n return get().db.tables.find((t) => t.tableName === tableName);\n },\n\n async refreshTableSchemas(): Promise<DataTable[]> {\n const newTables = await get().db.getTableSchemas();\n const currentTables = get().db.tables;\n\n // Only update if there's an actual change in the schemas\n if (!deepEquals(newTables, currentTables)) {\n set((state) =>\n produce(state, (draft) => {\n draft.db.tables = newTables;\n }),\n );\n }\n return newTables;\n },\n },\n };\n });\n}\n\ntype ProjectStateWithDuckDb = ProjectState<DuckDbSliceConfig> &\n DuckDbSliceState;\n\nexport function useStoreWithDuckDb<T>(\n selector: (state: ProjectStateWithDuckDb) => T,\n): T {\n return useBaseProjectStore<\n DuckDbSliceConfig,\n ProjectState<DuckDbSliceConfig>,\n T\n >((state) => selector(state as unknown as ProjectStateWithDuckDb));\n}\n"]}
1
+ {"version":3,"file":"DuckDbSlice.js","sourceRoot":"","sources":["../src/DuckDbSlice.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEf,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAC,OAAO,EAAC,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,EAAC,mBAAmB,EAAC,MAAM,kCAAkC,CAAC;AACrE,OAAO,EACL,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EAEtB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,mBAAmB,IAAI,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAGxE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;AACxC,cAAc;CACf,CAAC,CAAC;AAQH,MAAM,UAAU,yBAAyB;IACvC,OAAO;IACL,cAAc;KACf,CAAC;AACJ,CAAC;AAuMD;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,SAAS,GAAG,IAAI,mBAAmB,EAAE,GAGtC;IACC,OAAO,eAAe,CAAsC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACvE,OAAO;YACL,EAAE,EAAE;gBACF,SAAS,EAAE,kCAAkC;gBAC7C,MAAM,EAAE,MAAM,EAAE,uEAAuE;gBACvF,aAAa,EAAE,SAAS;gBACxB,eAAe,EAAE,SAAS;gBAC1B,wBAAwB,EAAE,KAAK;gBAC/B,MAAM,EAAE,EAAE;gBACV,cAAc,EAAE,EAAE;gBAClB,UAAU,EAAE,SAAS;gBACrB,UAAU,EAAE,EAAE;gBAEd,YAAY,EAAE,CAAC,SAA0B,EAAE,EAAE;oBAC3C,GAAG,CACD,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;wBAChB,KAAK,CAAC,MAAM,CAAC,WAAW,GAAG,EAAE,CAAC;wBAC9B,KAAK,CAAC,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC;oBACjC,CAAC,CAAC,CACH,CAAC;gBACJ,CAAC;gBAED,UAAU,EAAE,KAAK,IAAI,EAAE;oBACrB,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;oBACtC,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC;gBACvC,CAAC;gBAED,YAAY,EAAE,KAAK,IAAI,EAAE;oBACvB,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC;gBAC5B,CAAC;gBAED,OAAO,EAAE,KAAK,IAAI,EAAE;oBAClB,IAAI,CAAC;wBACH,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC;4BACvB,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;wBACrC,CAAC;oBACH,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;oBACtD,CAAC;gBACH,CAAC;gBAED,KAAK,CAAC,oBAAoB,CACxB,SAAsC,EACtC,KAAa;oBAEb,MAAM,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC;wBACnD,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,sBAAsB,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;oBAE/C,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC;oBAEhD,MAAM,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;oBAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAC5B,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;oBAC9D,CAAC;oBACD,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAW,CAAC;oBAC1C,MAAM,WAAW,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;oBAC9D,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;wBACtB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;oBAC3D,CAAC;oBAED,MAAM,QAAQ,GAAG,iBAAiB,CAChC,MAAM,SAAS,CAAC,KAAK,CACnB,2BAA2B,aAAa;gBACtC,UAAU,CAAC,CAAC,CAAC;cACf,CACD,CAAC,MAAM,CACT,CAAC;oBACF,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,CAAC;gBAC/B,CAAC;gBAED;;mBAEG;gBACH,KAAK,CAAC,SAAS,CAAC,MAAM;oBACpB,MAAM,YAAY,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAC,MAAM,EAAC,CAAC,CAAC;oBAC/D,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAChD,CAAC;gBAED;;mBAEG;gBACH,KAAK,CAAC,cAAc,CAAC,SAAiB,EAAE,MAAM,GAAG,MAAM;oBACrD,MAAM,QAAQ,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC;wBAC/C,MAAM;wBACN,KAAK,EAAE,SAAS;qBACjB,CAAC,CAAC;oBACH,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACrB,CAAC;gBAED;;mBAEG;gBACH,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;oBAC3C,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,iBAAiB,CAAC,EAAC,KAAK,EAAE,MAAM,EAAC,CAAC,CAAC;gBACrD,CAAC;gBAED,KAAK,CAAC,iBAAiB,CAAC,SAAsC;oBAC5D,MAAM,EAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAC,GAC7B,OAAO,SAAS,KAAK,QAAQ;wBAC3B,CAAC,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC;wBACpB,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;oBACtB,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC;oBAChD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAClC,wBAAwB,sBAAsB,CAAC;wBAC7C,MAAM;wBACN,QAAQ;wBACR,KAAK;qBACN,CAAC,EAAE,CACL,CAAC,MAAM,CAAC;oBACT,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;gBACnC,CAAC;gBAED;;mBAEG;gBACH,KAAK,CAAC,eAAe,CAAC,MAAM;oBAC1B,OAAO,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAC,MAAM,EAAC,CAAC,CAAC;gBACnD,CAAC;gBAED,KAAK,CAAC,gBAAgB,CACpB,MAA6C;oBAE7C,MAAM,EAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAC,GAAG,MAAM,IAAI,EAAE,CAAC;oBAC/C,MAAM,eAAe,GAAG,MAAM,SAAS,CAAC,KAAK,CAC3C;;;;cAKE,MAAM,IAAI,QAAQ,IAAI,KAAK;wBACzB,CAAC,CAAC,SAAS;4BACP,MAAM,CAAC,CAAC,CAAC,aAAa,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;4BAC9C,QAAQ,CAAC,CAAC,CAAC,eAAe,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;4BACpD,KAAK,CAAC,CAAC,CAAC,WAAW,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;yBAC3C;6BACE,MAAM,CAAC,OAAO,CAAC;6BACf,IAAI,CAAC,OAAO,CAAC,EAAE;wBACpB,CAAC,CAAC,EACN,EAAE,CACH,CAAC,MAAM,CAAC;oBAET,MAAM,SAAS,GAAgB,EAAE,CAAC;oBAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;wBACjD,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;wBAC9D,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;wBAC1D,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;wBACvD,MAAM,WAAW,GAAG,eAAe;6BAChC,QAAQ,CAAC,cAAc,CAAC;4BACzB,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;wBACX,MAAM,WAAW,GAAG,eAAe;6BAChC,QAAQ,CAAC,cAAc,CAAC;4BACzB,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;wBACX,MAAM,OAAO,GAAkB,EAAE,CAAC;wBAClC,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;4BAC/C,OAAO,CAAC,IAAI,CAAC;gCACX,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gCACzB,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;6BAC1B,CAAC,CAAC;wBACL,CAAC;wBACD,SAAS,CAAC,IAAI,CAAC;4BACb,KAAK,EAAE,sBAAsB,CAAC,EAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAC,CAAC;4BACxD,QAAQ;4BACR,MAAM;4BACN,SAAS,EAAE,KAAK;4BAChB,OAAO;yBACR,CAAC,CAAC;oBACL,CAAC;oBACD,OAAO,SAAS,CAAC;gBACnB,CAAC;gBAED,KAAK,CAAC,gBAAgB,CAAC,SAAsC;oBAC3D,MAAM,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC;wBACnD,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,sBAAsB,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;oBAC/C,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAClE,IAAI,CAAC,KAAK,EAAE,CAAC;wBACX,OAAO,KAAK,CAAC;oBACf,CAAC;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,KAAK,CAAC,SAAS,CAAC,SAAS;oBACvB,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC;oBAChD,MAAM,cAAc,GAAG,oBAAoB,CAAC,SAAS,CAAC;wBACpD,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,sBAAsB,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;oBAC/C,MAAM,SAAS,CAAC,KAAK,CAAC,wBAAwB,cAAc,GAAG,CAAC;yBAC7D,MAAM,CAAC;oBACV,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC;gBACvC,CAAC;gBAED,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI;oBAC5B,MAAM,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC;wBACnD,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,sBAAsB,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;oBAE/C,MAAM,EAAC,EAAE,EAAC,GAAG,GAAG,EAAE,CAAC;oBACnB,IAAI,IAAI,YAAY,KAAK,CAAC,KAAK,EAAE,CAAC;wBAChC,wCAAwC;wBACxC,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC;oBAC/D,CAAC;yBAAM,CAAC;wBACN,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,EAAE;4BAC7D,OAAO,EAAE,IAAI;yBACd,CAAC,CAAC;oBACL,CAAC;oBACD,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACd,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;oBACzC,CAAC;oBACD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;wBACvB,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;oBACjC,CAAC,CAAC,CACH,CAAC;oBACF,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC;oBACrC,OAAO,QAAQ,CAAC;gBAClB,CAAC;gBAED,KAAK,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ;oBACxC,MAAM,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC;wBACnD,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,sBAAsB,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;oBAC/C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;wBACvB,KAAK,CAAC,EAAE,CAAC,cAAc,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC;oBAC/D,CAAC,CAAC,CACH,CAAC;gBACJ,CAAC;gBAED,QAAQ,CAAC,SAAS;oBAChB,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;gBAC7C,CAAC;gBAED,eAAe,CAAC,SAAsC;oBACpD,MAAM,EAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAC,GAAG;wBAChC,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,aAAa;wBAC9B,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,eAAe;wBAClC,GAAG,CAAC,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC,CAAC,SAAS,CAAC;qBACpE,CAAC;oBACF,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CACzB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,KAAK;wBACvB,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC;wBACtC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAC/C,CAAC;gBACJ,CAAC;gBAED,KAAK,CAAC,mBAAmB;oBACvB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;wBACvB,KAAK,CAAC,EAAE,CAAC,wBAAwB,GAAG,IAAI,CAAC;oBAC3C,CAAC,CAAC,CACH,CAAC;oBACF,IAAI,CAAC;wBACH,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC;wBAChD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAClC,mEAAmE,CACpE,CAAC,MAAM,CAAC;wBACT,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;4BACvB,KAAK,CAAC,EAAE,CAAC,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;4BAC3D,KAAK,CAAC,EAAE,CAAC,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;wBACjE,CAAC,CAAC,CACH,CAAC;wBACF,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC;wBACpD,yDAAyD;wBACzD,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;4BAC5C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;gCACvB,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC;gCAC5B,KAAK,CAAC,EAAE,CAAC,WAAW,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;4BACxD,CAAC,CAAC,CACH,CAAC;wBACJ,CAAC;wBACD,OAAO,SAAS,CAAC;oBACnB,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,GAAG,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;wBACpC,OAAO,EAAE,CAAC;oBACZ,CAAC;4BAAS,CAAC;wBACT,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;4BACvB,KAAK,CAAC,EAAE,CAAC,wBAAwB,GAAG,KAAK,CAAC;wBAC5C,CAAC,CAAC,CACH,CAAC;oBACJ,CAAC;gBACH,CAAC;gBAED,KAAK,CAAC,eAAe,CAAC,GAAW;oBAC/B,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC;oBAChD,MAAM,WAAW,GAAG,CAClB,MAAM,SAAS,CAAC,KAAK,CACnB,6BAA6B,SAAS,CAAC,GAAG,CAAC,GAAG,CAC/C,CAAC,MAAM,CACT;yBACE,UAAU,CAAC,CAAC,CAAC;wBACd,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;oBACX,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBACjC,CAAC;gBAED,KAAK,CAAC,UAAU,CAAC,KAAa;oBAC5B,qCAAqC;oBACrC,MAAM,QAAQ,GAAG,GAAG,KAAK,EAAE,CAAC;oBAC5B,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC;oBAEhD,uDAAuD;oBACvD,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;oBACpD,IAAI,aAAa,EAAE,CAAC;wBAClB,OAAO,aAAa,CAAC;oBACvB,CAAC;oBAED,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBAC3C,qCAAqC;oBACrC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;wBACvB,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAC;oBAC9C,CAAC,CAAC,CACH,CAAC;oBAEF,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;wBAC9B,qCAAqC;wBACrC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACZ,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;4BACvB,OAAO,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;wBACvC,CAAC,CAAC,CACH,CAAC;oBACJ,CAAC,CAAC,CAAC;oBAEH,OAAO,WAAW,CAAC;gBACrB,CAAC;aACF;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAKD,MAAM,UAAU,kBAAkB,CAChC,QAA8C;IAE9C,OAAO,mBAAmB,CAIxB,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAA0C,CAAC,CAAC,CAAC;AACrE,CAAC","sourcesContent":["import {\n createBaseSlice,\n ProjectState,\n useBaseProjectStore,\n} from '@sqlrooms/project';\nimport * as arrow from 'apache-arrow';\nimport deepEquals from 'fast-deep-equal';\nimport {produce} from 'immer';\nimport {z} from 'zod';\nimport {StateCreator} from 'zustand';\nimport {DuckDbConnector, QueryHandle} from './connectors/DuckDbConnector';\nimport {WasmDuckDbConnector} from './connectors/WasmDuckDbConnector';\nimport {\n escapeId,\n escapeVal,\n getColValAsNumber,\n isQualifiedTableName,\n makeQualifiedTableName,\n QualifiedTableName,\n splitSqlStatements,\n} from './duckdb-utils';\nimport {createDbSchemaTrees as createDbSchemaTrees} from './schemaTree';\nimport {DataTable, TableColumn, DbSchemaNode} from './types';\n\nexport const DuckDbSliceConfig = z.object({\n // nothing yet\n});\nexport type DuckDbSliceConfig = z.infer<typeof DuckDbSliceConfig>;\n\nexport type SchemaAndDatabase = {\n schema?: string;\n database?: string;\n};\n\nexport function createDefaultDuckDbConfig(): DuckDbSliceConfig {\n return {\n // nothing yet\n };\n}\n\n/**\n * State and actions for the DuckDB slice\n */\nexport type DuckDbSliceState = {\n db: {\n /**\n * The DuckDB connector instance\n */\n connector: DuckDbConnector;\n /**\n * @deprecated We shouldn't limit the schema to a single one.\n */\n schema: string;\n\n currentSchema: string | undefined;\n currentDatabase: string | undefined;\n\n /**\n * Cache of refreshed table schemas\n */\n tables: DataTable[];\n /**\n * Cache of row counts for tables\n */\n tableRowCounts: {[tableName: string]: number};\n /**\n * Cache of schema trees for tables\n */\n schemaTrees?: DbSchemaNode[];\n /**\n * Cache of currently running query handles\n */\n queryCache: {[key: string]: QueryHandle};\n /**\n * Whether the table schemas are being refreshed\n */\n isRefreshingTableSchemas: boolean;\n\n /**\n * Set a new DuckDB connector\n */\n setConnector: (connector: DuckDbConnector) => void;\n\n /**\n * Initialize the connector (creates a WasmDuckDbConnector if none exists)\n */\n initialize: () => Promise<void>;\n\n /**\n * Close and clean up the connector\n */\n destroy: () => Promise<void>;\n\n /**\n * Add a table to the project.\n * @param tableName - The name of the table to add.\n * @param data - The data to add to the table: an arrow table or an array of records.\n * @returns A promise that resolves to the table that was added.\n */\n addTable(\n tableName: string | QualifiedTableName,\n data: arrow.Table | Record<string, unknown>[],\n ): Promise<DataTable>;\n\n /**\n * Load the schemas of the tables in the database.\n */\n loadTableSchemas(\n filter?: SchemaAndDatabase & {table?: string},\n ): Promise<DataTable[]>;\n\n /**\n * @deprecated Use findTableByName instead\n */\n getTable(tableName: string): DataTable | undefined;\n\n /**\n * @internal Avoid using this directly, it's for internal use.\n */\n setTableRowCount(\n tableName: string | QualifiedTableName,\n rowCount: number,\n ): void;\n\n /**\n * Find a table by name in the last refreshed table schemas.\n * If no schema or database is provided, the table will be found in the current schema\n * and database (from last table schemas refresh).\n * @param tableName - The name of the table to find or a qualified table name.\n * @returns The table or undefined if not found.\n */\n findTableByName(\n tableName: string | QualifiedTableName,\n ): DataTable | undefined;\n\n /**\n * Refresh table schemas from the database.\n * @returns A promise that resolves to the updated tables.\n */\n refreshTableSchemas(): Promise<DataTable[]>;\n /**\n * Get the connector. If it's not initialized, it will be initialized.\n */\n getConnector: () => Promise<DuckDbConnector>;\n\n /**\n * @deprecated Use .loadTableRowCount() instead\n */\n getTableRowCount: (table: string, schema?: string) => Promise<number>;\n\n /**\n * Load the row count of a table\n */\n loadTableRowCount: (\n tableName: string | QualifiedTableName,\n ) => Promise<number>;\n\n /**\n * Execute a query with query handle (not result) caching and deduplication\n * @param query - The SQL query to execute\n * @returns The QueryHandle for the query or null if disabled\n */\n executeSql: (query: string) => Promise<QueryHandle | null>;\n\n /**\n * @deprecated Use .tables or .loadTableSchemas() instead\n */\n getTables: (schema?: string) => Promise<string[]>;\n\n /**\n * @deprecated Use .loadTableSchemas() instead\n */\n getTableSchema: (\n tableName: string,\n schema?: string,\n ) => Promise<DataTable | undefined>;\n\n /**\n * @deprecated Use .tables or .loadTableSchemas() instead\n */\n getTableSchemas: (schema?: string) => Promise<DataTable[]>;\n\n /**\n * Check if a table exists\n */\n checkTableExists: (\n tableName: string | QualifiedTableName,\n ) => Promise<boolean>;\n\n /**\n * Delete a table with optional schema and database\n * @param tableName - The name of the table to delete\n * @param options - Optional parameters including schema and database\n */\n dropTable: (tableName: string | QualifiedTableName) => Promise<void>;\n\n /**\n * Create a table from a query.\n * @param tableName - The name of the table to create.\n * @param query - The query to create the table from.\n * @returns The table that was created.\n */\n createTableFromQuery: (\n tableName: string | QualifiedTableName,\n query: string,\n ) => Promise<{tableName: string | QualifiedTableName; rowCount: number}>;\n\n /**\n * Parse a SQL SELECT statement to JSON\n * @param sql - The SQL SELECT statement to parse.\n * @returns A promise that resolves to the parsed JSON.\n */\n sqlSelectToJson: (sql: string) => Promise<\n | {\n error: true;\n error_type: string;\n error_message: string;\n error_subtype: string;\n position: string;\n }\n | {\n error: false;\n statements: {\n node: {\n from_table: {\n alias: string;\n show_type: string;\n table_name: string;\n };\n select_list: Record<string, unknown>[];\n };\n }[];\n }\n >;\n };\n};\n\n/**\n * Create a DuckDB slice for managing the connector\n */\nexport function createDuckDbSlice({\n connector = new WasmDuckDbConnector(),\n}: {\n connector?: DuckDbConnector;\n}): StateCreator<DuckDbSliceState> {\n return createBaseSlice<DuckDbSliceConfig, DuckDbSliceState>((set, get) => {\n return {\n db: {\n connector, // Will be initialized during init\n schema: 'main', // TODO: remove schema, we should not limit the schema to a single one.\n currentSchema: undefined,\n currentDatabase: undefined,\n isRefreshingTableSchemas: false,\n tables: [],\n tableRowCounts: {},\n schemaTree: undefined,\n queryCache: {},\n\n setConnector: (connector: DuckDbConnector) => {\n set(\n produce((state) => {\n state.config.dataSources = [];\n state.db.connector = connector;\n }),\n );\n },\n\n initialize: async () => {\n await get().db.connector.initialize();\n await get().db.refreshTableSchemas();\n },\n\n getConnector: async () => {\n return get().db.connector;\n },\n\n destroy: async () => {\n try {\n if (get().db.connector) {\n await get().db.connector.destroy();\n }\n } catch (err) {\n console.error('Error during DuckDB shutdown:', err);\n }\n },\n\n async createTableFromQuery(\n tableName: string | QualifiedTableName,\n query: string,\n ) {\n const qualifiedName = isQualifiedTableName(tableName)\n ? tableName\n : makeQualifiedTableName({table: tableName});\n\n const connector = await get().db.getConnector();\n\n const statements = splitSqlStatements(query);\n if (statements.length !== 1) {\n throw new Error('Query must contain exactly one statement');\n }\n const statement = statements[0] as string;\n const parsedQuery = await get().db.sqlSelectToJson(statement);\n if (parsedQuery.error) {\n throw new Error('Query is not a valid SELECT statement');\n }\n\n const rowCount = getColValAsNumber(\n await connector.query(\n `CREATE OR REPLACE TABLE ${qualifiedName} AS (\n ${statements[0]}\n )`,\n ).result,\n );\n return {tableName, rowCount};\n },\n\n /**\n * @deprecated Use .tables or .loadTableSchemas() instead\n */\n async getTables(schema) {\n const tableSchemas = await get().db.loadTableSchemas({schema});\n return tableSchemas.map((t) => t.table.table);\n },\n\n /**\n * @deprecated Use .loadTableSchemas() instead\n */\n async getTableSchema(tableName: string, schema = 'main') {\n const newLocal = await get().db.loadTableSchemas({\n schema,\n table: tableName,\n });\n return newLocal[0];\n },\n\n /**\n * @deprecated Use .loadTableRowCount() instead\n */\n async getTableRowCount(table, schema = 'main') {\n return get().db.loadTableRowCount({table, schema});\n },\n\n async loadTableRowCount(tableName: string | QualifiedTableName) {\n const {schema, database, table} =\n typeof tableName === 'string'\n ? {table: tableName}\n : tableName || {};\n const connector = await get().db.getConnector();\n const result = await connector.query(\n `SELECT COUNT(*) FROM ${makeQualifiedTableName({\n schema,\n database,\n table,\n })}`,\n ).result;\n return getColValAsNumber(result);\n },\n\n /**\n * @deprecated Use .loadTableSchemas() instead\n */\n async getTableSchemas(schema) {\n return await get().db.loadTableSchemas({schema});\n },\n\n async loadTableSchemas(\n filter?: SchemaAndDatabase & {table?: string},\n ): Promise<DataTable[]> {\n const {schema, database, table} = filter || {};\n const describeResults = await connector.query(\n `FROM (DESCRIBE)\n SELECT \n database, schema,\n name, column_names, column_types\n ${\n schema || database || table\n ? `WHERE ${[\n schema ? `schema = '${escapeId(schema)}'` : '',\n database ? `database = '${escapeId(database)}'` : '',\n table ? `name = '${escapeId(table)}'` : '',\n ]\n .filter(Boolean)\n .join(' AND ')}`\n : ''\n }`,\n ).result;\n\n const newTables: DataTable[] = [];\n for (let i = 0; i < describeResults.numRows; i++) {\n const database = describeResults.getChild('database')?.get(i);\n const schema = describeResults.getChild('schema')?.get(i);\n const table = describeResults.getChild('name')?.get(i);\n const columnNames = describeResults\n .getChild('column_names')\n ?.get(i);\n const columnTypes = describeResults\n .getChild('column_types')\n ?.get(i);\n const columns: TableColumn[] = [];\n for (let di = 0; di < columnNames.length; di++) {\n columns.push({\n name: columnNames.get(di),\n type: columnTypes.get(di),\n });\n }\n newTables.push({\n table: makeQualifiedTableName({database, schema, table}),\n database,\n schema,\n tableName: table,\n columns,\n });\n }\n return newTables;\n },\n\n async checkTableExists(tableName: string | QualifiedTableName) {\n const qualifiedName = isQualifiedTableName(tableName)\n ? tableName\n : makeQualifiedTableName({table: tableName});\n const table = (await get().db.loadTableSchemas(qualifiedName))[0];\n if (!table) {\n return false;\n }\n return true;\n },\n\n async dropTable(tableName): Promise<void> {\n const connector = await get().db.getConnector();\n const qualifiedTable = isQualifiedTableName(tableName)\n ? tableName\n : makeQualifiedTableName({table: tableName});\n await connector.query(`DROP TABLE IF EXISTS ${qualifiedTable};`)\n .result;\n await get().db.refreshTableSchemas();\n },\n\n async addTable(tableName, data) {\n const qualifiedName = isQualifiedTableName(tableName)\n ? tableName\n : makeQualifiedTableName({table: tableName});\n\n const {db} = get();\n if (data instanceof arrow.Table) {\n // TODO: make sure the table is replaced\n await db.connector.loadArrow(data, qualifiedName.toString());\n } else {\n await db.connector.loadObjects(data, qualifiedName.toString(), {\n replace: true,\n });\n }\n const newTable = (await db.loadTableSchemas(qualifiedName))[0];\n if (!newTable) {\n throw new Error('Failed to add table');\n }\n set((state) =>\n produce(state, (draft) => {\n draft.db.tables.push(newTable);\n }),\n );\n await get().db.refreshTableSchemas();\n return newTable;\n },\n\n async setTableRowCount(tableName, rowCount) {\n const qualifiedName = isQualifiedTableName(tableName)\n ? tableName\n : makeQualifiedTableName({table: tableName});\n set((state) =>\n produce(state, (draft) => {\n draft.db.tableRowCounts[qualifiedName.toString()] = rowCount;\n }),\n );\n },\n\n getTable(tableName) {\n return get().db.findTableByName(tableName);\n },\n\n findTableByName(tableName: string | QualifiedTableName) {\n const {table, schema, database} = {\n schema: get().db.currentSchema,\n database: get().db.currentDatabase,\n ...(typeof tableName === 'string' ? {table: tableName} : tableName),\n };\n return get().db.tables.find(\n (t) =>\n t.table.table === table &&\n (!schema || t.table.schema === schema) &&\n (!database || t.table.database === database),\n );\n },\n\n async refreshTableSchemas(): Promise<DataTable[]> {\n set((state) =>\n produce(state, (draft) => {\n draft.db.isRefreshingTableSchemas = true;\n }),\n );\n try {\n const connector = await get().db.getConnector();\n const result = await connector.query(\n `SELECT current_schema() AS schema, current_database() AS database`,\n ).result;\n set((state) =>\n produce(state, (draft) => {\n draft.db.currentSchema = result.getChild('schema')?.get(0);\n draft.db.currentDatabase = result.getChild('database')?.get(0);\n }),\n );\n const newTables = await get().db.loadTableSchemas();\n // Only update if there's an actual change in the schemas\n if (!deepEquals(newTables, get().db.tables)) {\n set((state) =>\n produce(state, (draft) => {\n draft.db.tables = newTables;\n draft.db.schemaTrees = createDbSchemaTrees(newTables);\n }),\n );\n }\n return newTables;\n } catch (err) {\n get().project.captureException(err);\n return [];\n } finally {\n set((state) =>\n produce(state, (draft) => {\n draft.db.isRefreshingTableSchemas = false;\n }),\n );\n }\n },\n\n async sqlSelectToJson(sql: string) {\n const connector = await get().db.getConnector();\n const parsedQuery = (\n await connector.query(\n `SELECT json_serialize_sql(${escapeVal(sql)})`,\n ).result\n )\n .getChildAt(0)\n ?.get(0);\n return JSON.parse(parsedQuery);\n },\n\n async executeSql(query: string): Promise<QueryHandle | null> {\n // Create a unique key for this query\n const queryKey = `${query}`;\n const connector = await get().db.getConnector();\n\n // Check if we already have a cached query for this key\n const existingQuery = get().db.queryCache[queryKey];\n if (existingQuery) {\n return existingQuery;\n }\n\n const queryHandle = connector.query(query);\n // Cache the query handle immediately\n set((state) =>\n produce(state, (draft) => {\n draft.db.queryCache[queryKey] = queryHandle;\n }),\n );\n\n queryHandle.result.finally(() => {\n // remove from cache after completion\n set((state) =>\n produce(state, (draft) => {\n delete draft.db.queryCache[queryKey];\n }),\n );\n });\n\n return queryHandle;\n },\n },\n };\n });\n}\n\ntype ProjectStateWithDuckDb = ProjectState<DuckDbSliceConfig> &\n DuckDbSliceState;\n\nexport function useStoreWithDuckDb<T>(\n selector: (state: ProjectStateWithDuckDb) => T,\n): T {\n return useBaseProjectStore<\n DuckDbSliceConfig,\n ProjectState<DuckDbSliceConfig>,\n T\n >((state) => selector(state as unknown as ProjectStateWithDuckDb));\n}\n"]}