@malloydata/db-duckdb 0.0.337 → 0.0.339

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,4 +1,4 @@
1
- import type { MalloyQueryData, PersistSQLResults, PooledConnection, QueryDataRow, QueryOptionsReader, QueryRunStats, RunSQLOptions, StreamingConnection, StructDef, TestableConnection, SQLSourceDef, TableSourceDef, SQLSourceRequest } from '@malloydata/malloy';
1
+ import type { MalloyQueryData, PersistSQLResults, PooledConnection, QueryRecord, QueryOptionsReader, QueryRunStats, RunSQLOptions, StreamingConnection, StructDef, TestableConnection, SQLSourceDef, TableSourceDef, SQLSourceRequest } from '@malloydata/malloy';
2
2
  import { BaseConnection } from '@malloydata/malloy/connection';
3
3
  export interface DuckDBQueryOptions {
4
4
  rowLimit: number;
@@ -7,6 +7,7 @@ export declare abstract class DuckDBCommon extends BaseConnection implements Tes
7
7
  protected queryOptions?: QueryOptionsReader | undefined;
8
8
  protected isMotherDuck: boolean;
9
9
  protected motherDuckToken: string | undefined;
10
+ protected setupSQL: string | undefined;
10
11
  private readonly dialect;
11
12
  static DEFAULT_QUERY_OPTIONS: DuckDBQueryOptions;
12
13
  readonly name: string;
@@ -18,15 +19,15 @@ export declare abstract class DuckDBCommon extends BaseConnection implements Tes
18
19
  abstract getDigest(): string;
19
20
  protected abstract setup(): Promise<void>;
20
21
  protected abstract runDuckDBQuery(sql: string): Promise<{
21
- rows: QueryDataRow[];
22
+ rows: QueryRecord[];
22
23
  totalRows: number;
23
24
  }>;
24
25
  runRawSQL(sql: string): Promise<{
25
- rows: QueryDataRow[];
26
+ rows: QueryRecord[];
26
27
  totalRows: number;
27
28
  }>;
28
29
  runSQL(sql: string, options?: RunSQLOptions): Promise<MalloyQueryData>;
29
- abstract runSQLStream(sql: string, options: RunSQLOptions): AsyncIterableIterator<QueryDataRow>;
30
+ abstract runSQLStream(sql: string, options: RunSQLOptions): AsyncIterableIterator<QueryRecord>;
30
31
  fetchSelectSchema(sqlRef: SQLSourceRequest): Promise<SQLSourceDef | string>;
31
32
  estimateQueryCost(_: string): Promise<QueryRunStats>;
32
33
  fillStructDefFromTypeMap(structDef: StructDef, typeMap: {
@@ -36,7 +37,6 @@ export declare abstract class DuckDBCommon extends BaseConnection implements Tes
36
37
  fetchTableSchema(tableKey: string, tablePath: string): Promise<TableSourceDef>;
37
38
  canStream(): this is StreamingConnection;
38
39
  test(): Promise<void>;
39
- abstract createHash(sqlCommand: string): Promise<string>;
40
40
  manifestTemporaryTable(sqlCommand: string): Promise<string>;
41
41
  abstract close(): Promise<void>;
42
42
  }
@@ -137,8 +137,8 @@ class DuckDBCommon extends connection_1.BaseConnection {
137
137
  await this.runRawSQL('SELECT 1');
138
138
  }
139
139
  async manifestTemporaryTable(sqlCommand) {
140
- const hash = await this.createHash(sqlCommand);
141
- const tableName = `tt${hash}`;
140
+ const hash = (0, malloy_1.makeDigest)(sqlCommand);
141
+ const tableName = `tt${hash.slice(0, this.dialect.maxIdentifierLength - 2)}`;
142
142
  const cmd = `CREATE TEMPORARY TABLE IF NOT EXISTS ${tableName} AS (${sqlCommand});`;
143
143
  // console.log(cmd);
144
144
  await this.runRawSQL(cmd);
@@ -1,13 +1,14 @@
1
1
  import { DuckDBCommon } from './duckdb_common';
2
2
  import { DuckDBInstance } from '@duckdb/node-api';
3
3
  import type { DuckDBConnection as DuckDBNodeConnection } from '@duckdb/node-api';
4
- import type { ConnectionConfig, QueryDataRow, QueryOptionsReader, RunSQLOptions } from '@malloydata/malloy';
4
+ import type { ConnectionConfig, QueryRecord, QueryOptionsReader, RunSQLOptions } from '@malloydata/malloy';
5
5
  export interface DuckDBConnectionOptions extends ConnectionConfig {
6
6
  additionalExtensions?: string[];
7
7
  databasePath?: string;
8
8
  motherDuckToken?: string;
9
9
  workingDirectory?: string;
10
10
  readOnly?: boolean;
11
+ setupSQL?: string;
11
12
  }
12
13
  interface ActiveDB {
13
14
  instance: DuckDBInstance;
@@ -31,11 +32,10 @@ export declare class DuckDBConnection extends DuckDBCommon {
31
32
  loadExtension(ext: string): Promise<void>;
32
33
  protected setup(): Promise<void>;
33
34
  protected runDuckDBQuery(sql: string): Promise<{
34
- rows: QueryDataRow[];
35
+ rows: QueryRecord[];
35
36
  totalRows: number;
36
37
  }>;
37
- runSQLStream(sql: string, { rowLimit, abortSignal }?: RunSQLOptions): AsyncIterableIterator<QueryDataRow>;
38
- createHash(sqlCommand: string): Promise<string>;
38
+ runSQLStream(sql: string, { rowLimit, abortSignal }?: RunSQLOptions): AsyncIterableIterator<QueryRecord>;
39
39
  close(): Promise<void>;
40
40
  /**
41
41
  * Forcefully close all cached DuckDB instances. Useful for test cleanup
@@ -70,6 +70,9 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
70
70
  if (Array.isArray(arg.additionalExtensions)) {
71
71
  this.additionalExtensions = arg.additionalExtensions;
72
72
  }
73
+ if (typeof arg.setupSQL === 'string') {
74
+ this.setupSQL = arg.setupSQL;
75
+ }
73
76
  }
74
77
  if (this.databasePath === ':memory:') {
75
78
  this.readOnly = false;
@@ -80,8 +83,7 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
80
83
  this.connecting = this.init();
81
84
  }
82
85
  getDigest() {
83
- const data = `duckdb:${this.databasePath}:${this.workingDirectory}`;
84
- return (0, malloy_1.makeDigest)(data);
86
+ return (0, malloy_1.makeDigest)('duckdb', this.databasePath, this.workingDirectory, this.setupSQL);
85
87
  }
86
88
  async init() {
87
89
  try {
@@ -161,6 +163,14 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
161
163
  console.error(`duckdb setup ${cmd} => ${error}`);
162
164
  }
163
165
  }
166
+ if (this.setupSQL) {
167
+ for (const stmt of this.setupSQL.split(';\n')) {
168
+ const trimmed = stmt.trim();
169
+ if (trimmed) {
170
+ await this.runDuckDBQuery(trimmed);
171
+ }
172
+ }
173
+ }
164
174
  };
165
175
  await this.connecting;
166
176
  if (!this.isSetup) {
@@ -205,9 +215,6 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
205
215
  }
206
216
  }
207
217
  }
208
- async createHash(sqlCommand) {
209
- return (0, malloy_1.makeDigest)(sqlCommand);
210
- }
211
218
  async close() {
212
219
  const activeDB = DuckDBConnection.activeDBs[this.databasePath];
213
220
  if (activeDB) {
@@ -1,5 +1,5 @@
1
1
  import * as duckdb from '@duckdb/duckdb-wasm';
2
- import type { FetchSchemaOptions, QueryDataRow, QueryOptionsReader, RunSQLOptions, SQLSourceDef, ConnectionConfig, TableSourceDef, SQLSourceRequest } from '@malloydata/malloy';
2
+ import type { FetchSchemaOptions, QueryRecord, QueryOptionsReader, RunSQLOptions, SQLSourceDef, ConnectionConfig, TableSourceDef, SQLSourceRequest } from '@malloydata/malloy';
3
3
  import { DuckDBCommon } from './duckdb_common';
4
4
  type RemoteFileCallback = (tableName: string) => Promise<Uint8Array | undefined>;
5
5
  export interface DuckDBWasmOptions extends ConnectionConfig {
@@ -7,6 +7,7 @@ export interface DuckDBWasmOptions extends ConnectionConfig {
7
7
  databasePath?: string;
8
8
  motherDuckToken: string | undefined;
9
9
  workingDirectory?: string;
10
+ setupSQL?: string;
10
11
  }
11
12
  export declare abstract class DuckDBWASMConnection extends DuckDBCommon {
12
13
  readonly arg: string | DuckDBWasmOptions;
@@ -31,10 +32,10 @@ export declare abstract class DuckDBWASMConnection extends DuckDBCommon {
31
32
  loadExtension(ext: string): Promise<void>;
32
33
  protected setup(): Promise<void>;
33
34
  protected runDuckDBQuery(sql: string, abortSignal?: AbortSignal): Promise<{
34
- rows: QueryDataRow[];
35
+ rows: QueryRecord[];
35
36
  totalRows: number;
36
37
  }>;
37
- runSQLStream(sql: string, { rowLimit, abortSignal }?: RunSQLOptions): AsyncIterableIterator<QueryDataRow>;
38
+ runSQLStream(sql: string, { rowLimit, abortSignal }?: RunSQLOptions): AsyncIterableIterator<QueryRecord>;
38
39
  private findTables;
39
40
  fetchSchemaForSQLStruct(sqlRef: SQLSourceRequest, options: FetchSchemaOptions): Promise<{
40
41
  structDef: SQLSourceDef;
@@ -161,18 +161,24 @@ function unwrapStruct(value, children) {
161
161
  return result;
162
162
  }
163
163
  function unwrapPrimitive(value) {
164
+ if (value === null || value === undefined)
165
+ return null;
164
166
  if (value instanceof Date)
165
167
  return value;
166
168
  if (typeof value === 'bigint')
167
169
  return safeNumber(value);
168
- if (typeof value !== 'object' || value === null)
170
+ if (typeof value === 'string' || typeof value === 'number')
171
+ return value;
172
+ if (typeof value === 'boolean')
169
173
  return value;
174
+ if (typeof value !== 'object')
175
+ return String(value);
170
176
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
177
  const obj = value;
172
178
  if (obj[Symbol.toPrimitive]) {
173
179
  return safeNumber(obj[Symbol.toPrimitive]());
174
180
  }
175
- return value;
181
+ return String(value);
176
182
  }
177
183
  function safeNumber(value) {
178
184
  if (typeof value === 'number') {
@@ -209,19 +215,18 @@ function formatDecimalString(str, scale, isNegative) {
209
215
  return isNegative ? '-' + result : result;
210
216
  }
211
217
  /**
212
- * Process a single Arrow result row into a Malloy QueryDataRow.
218
+ * Process a single Arrow result row into a Malloy QueryRecord.
213
219
  */
214
220
  function unwrapRow(row, schema) {
215
221
  const json = row.toJSON();
216
222
  const result = {};
217
223
  for (const field of schema.fields) {
218
- // Cast is safe: unwrapValue returns QueryValue-compatible types
219
224
  result[field.name] = unwrapValue(json[field.name], field.type);
220
225
  }
221
226
  return result;
222
227
  }
223
228
  /**
224
- * Process a DuckDB Table into an array of Malloy QueryDataRows.
229
+ * Process a DuckDB Table into an array of Malloy QueryRecords.
225
230
  */
226
231
  function unwrapTable(table) {
227
232
  return table.toArray().map(row => unwrapRow(row, table.schema));
@@ -269,6 +274,9 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
269
274
  if (Array.isArray(arg.additionalExtensions)) {
270
275
  this.additionalExtensions = arg.additionalExtensions;
271
276
  }
277
+ if (typeof arg.setupSQL === 'string') {
278
+ this.setupSQL = arg.setupSQL;
279
+ }
272
280
  }
273
281
  this.isMotherDuck =
274
282
  ((_a = this.databasePath) === null || _a === void 0 ? void 0 : _a.startsWith('md:')) ||
@@ -278,7 +286,7 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
278
286
  }
279
287
  getDigest() {
280
288
  var _a;
281
- return (0, malloy_1.makeDigest)('duckdb-wasm', (_a = this.databasePath) !== null && _a !== void 0 ? _a : ':memory:', this.workingDirectory);
289
+ return (0, malloy_1.makeDigest)('duckdb-wasm', (_a = this.databasePath) !== null && _a !== void 0 ? _a : ':memory:', this.workingDirectory, this.setupSQL);
282
290
  }
283
291
  async init() {
284
292
  // Select a bundle based on browser checks
@@ -342,6 +350,14 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
342
350
  console.error(`duckdb setup ${cmd} => ${error}`);
343
351
  }
344
352
  }
353
+ if (this.setupSQL) {
354
+ for (const stmt of this.setupSQL.split(';\n')) {
355
+ const trimmed = stmt.trim();
356
+ if (trimmed) {
357
+ await this.runDuckDBQuery(trimmed);
358
+ }
359
+ }
360
+ }
345
361
  };
346
362
  await this.connecting;
347
363
  if (!this.isSetup) {
@@ -1,15 +1,14 @@
1
1
  import * as duckdb from '@duckdb/duckdb-wasm';
2
2
  import { DuckDBWASMConnection as DuckDBWASMConnectionBase } from './duckdb_wasm_connection';
3
3
  import { MDConnection } from '@motherduck/wasm-client';
4
- import type { QueryDataRow } from '@malloydata/malloy';
4
+ import type { QueryRecord } from '@malloydata/malloy';
5
5
  export declare class DuckDBWASMConnection extends DuckDBWASMConnectionBase {
6
6
  protected _mdConnection: MDConnection | null;
7
7
  getBundles(): duckdb.DuckDBBundles;
8
8
  init(): Promise<void>;
9
9
  setup(): Promise<void>;
10
10
  protected runDuckDBQuery(sql: string, abortSignal?: AbortSignal): Promise<{
11
- rows: QueryDataRow[];
11
+ rows: QueryRecord[];
12
12
  totalRows: number;
13
13
  }>;
14
- createHash(sqlCommand: string): Promise<string>;
15
14
  }
@@ -59,7 +59,6 @@ exports.DuckDBWASMConnection = void 0;
59
59
  const duckdb = __importStar(require("@duckdb/duckdb-wasm"));
60
60
  const duckdb_wasm_connection_1 = require("./duckdb_wasm_connection");
61
61
  const wasm_client_1 = require("@motherduck/wasm-client");
62
- const malloy_1 = require("@malloydata/malloy");
63
62
  function unwrapMotherDuck(value) {
64
63
  let result = null;
65
64
  if (value !== null && typeof value === 'object') {
@@ -198,9 +197,6 @@ class DuckDBWASMConnection extends duckdb_wasm_connection_1.DuckDBWASMConnection
198
197
  return super.runDuckDBQuery(sql, abortSignal);
199
198
  }
200
199
  }
201
- async createHash(sqlCommand) {
202
- return (0, malloy_1.makeDigest)(sqlCommand);
203
- }
204
200
  }
205
201
  exports.DuckDBWASMConnection = DuckDBWASMConnection;
206
202
  //# sourceMappingURL=duckdb_wasm_connection_browser.js.map
@@ -2,5 +2,4 @@ import type { DuckDBBundles } from '@duckdb/duckdb-wasm';
2
2
  import { DuckDBWASMConnection as DuckDBWASMConnectionBase } from './duckdb_wasm_connection';
3
3
  export declare class DuckDBWASMConnection extends DuckDBWASMConnectionBase {
4
4
  getBundles(): DuckDBBundles;
5
- createHash(sqlCommand: string): Promise<string>;
6
5
  }
@@ -24,7 +24,6 @@
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.DuckDBWASMConnection = void 0;
26
26
  const duckdb_wasm_connection_1 = require("./duckdb_wasm_connection");
27
- const malloy_1 = require("@malloydata/malloy");
28
27
  class DuckDBWASMConnection extends duckdb_wasm_connection_1.DuckDBWASMConnection {
29
28
  getBundles() {
30
29
  const resolvePath = require.resolve('@duckdb/duckdb-wasm');
@@ -47,9 +46,6 @@ class DuckDBWASMConnection extends duckdb_wasm_connection_1.DuckDBWASMConnection
47
46
  },
48
47
  };
49
48
  }
50
- async createHash(sqlCommand) {
51
- return (0, malloy_1.makeDigest)(sqlCommand);
52
- }
53
49
  }
54
50
  exports.DuckDBWASMConnection = DuckDBWASMConnection;
55
51
  //# sourceMappingURL=duckdb_wasm_connection_node.js.map
package/dist/index.js CHANGED
@@ -35,6 +35,13 @@ const duckdb_connection_2 = require("./duckdb_connection");
35
35
  options['databasePath'] = options['path'];
36
36
  delete options['path'];
37
37
  }
38
+ // Parse comma-separated extensions string into array
39
+ if (typeof options['additionalExtensions'] === 'string') {
40
+ options['additionalExtensions'] = options['additionalExtensions']
41
+ .split(',')
42
+ .map(s => s.trim())
43
+ .filter(s => s.length > 0);
44
+ }
38
45
  return new duckdb_connection_2.DuckDBConnection(options);
39
46
  },
40
47
  properties: [
@@ -63,6 +70,8 @@ const duckdb_connection_2 = require("./duckdb_connection");
63
70
  displayName: 'Additional Extensions',
64
71
  type: 'string',
65
72
  optional: true,
73
+ description: 'Comma-separated list of DuckDB extensions to load (e.g. "spatial,fts"). ' +
74
+ 'These are loaded in addition to the built-in extensions: json, httpfs, icu.',
66
75
  },
67
76
  {
68
77
  name: 'readOnly',
@@ -70,6 +79,13 @@ const duckdb_connection_2 = require("./duckdb_connection");
70
79
  type: 'boolean',
71
80
  optional: true,
72
81
  },
82
+ {
83
+ name: 'setupSQL',
84
+ displayName: 'Setup SQL',
85
+ type: 'text',
86
+ optional: true,
87
+ description: 'SQL statements to run when the connection is established',
88
+ },
73
89
  ],
74
90
  });
75
91
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/db-duckdb",
3
- "version": "0.0.337",
3
+ "version": "0.0.339",
4
4
  "license": "MIT",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@duckdb/duckdb-wasm": "1.33.1-dev13.0",
48
48
  "@duckdb/node-api": "1.4.3-r.1",
49
- "@malloydata/malloy": "0.0.337",
49
+ "@malloydata/malloy": "0.0.339",
50
50
  "@motherduck/wasm-client": "^0.6.6",
51
51
  "apache-arrow": "^17.0.0",
52
52
  "web-worker": "^1.3.0"