@malloydata/db-duckdb 0.0.130-dev240311192728 → 0.0.130-dev240312011825

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.
@@ -4,6 +4,8 @@ export interface DuckDBQueryOptions {
4
4
  }
5
5
  export declare abstract class DuckDBCommon implements TestableConnection, PersistSQLResults, StreamingConnection {
6
6
  protected queryOptions?: QueryOptionsReader | undefined;
7
+ protected isMotherDuck: boolean;
8
+ protected motherDuckToken: string | undefined;
7
9
  private readonly dialect;
8
10
  static DEFAULT_QUERY_OPTIONS: DuckDBQueryOptions;
9
11
  private schemaCache;
@@ -51,6 +51,7 @@ class DuckDBCommon {
51
51
  }
52
52
  constructor(queryOptions) {
53
53
  this.queryOptions = queryOptions;
54
+ this.isMotherDuck = false;
54
55
  this.dialect = new malloy_1.DuckDBDialect();
55
56
  this.schemaCache = new Map();
56
57
  this.sqlSchemaCache = new Map();
@@ -17,8 +17,6 @@ export declare class DuckDBConnection extends DuckDBCommon {
17
17
  private additionalExtensions;
18
18
  private databasePath;
19
19
  private workingDirectory;
20
- private isMotherDuck;
21
- private motherDuckToken;
22
20
  private readOnly;
23
21
  connecting: Promise<void>;
24
22
  protected connection: Connection | null;
@@ -35,7 +35,6 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
35
35
  this.additionalExtensions = [];
36
36
  this.databasePath = ':memory:';
37
37
  this.workingDirectory = '.';
38
- this.isMotherDuck = false;
39
38
  this.readOnly = false;
40
39
  this.connection = null;
41
40
  if (typeof arg === 'string') {
@@ -1,13 +1,34 @@
1
- import * as duckdb from '@malloydata/duckdb-wasm';
1
+ import * as duckdb from '@duckdb/duckdb-wasm';
2
2
  import { FetchSchemaOptions, QueryDataRow, QueryOptionsReader, RunSQLOptions, StructDef, SQLBlock, ConnectionConfig } from '@malloydata/malloy';
3
+ import { StructRow } from 'apache-arrow';
3
4
  import { DuckDBCommon } from './duckdb_common';
5
+ /**
6
+ * Arrow's toJSON() doesn't really do what I'd expect, since
7
+ * it still includes Arrow objects like DecimalBigNums and Vectors,
8
+ * so we need this fairly gross function to unwrap those.
9
+ *
10
+ * @param value Element from an Arrow StructRow.
11
+ * @return Vanilla Javascript value
12
+ */
13
+ export declare const unwrapArrow: (value: unknown) => any;
14
+ /**
15
+ * Process a single Arrow result row into a Malloy QueryDataRow
16
+ * Unfortunately simply calling JSONParse(JSON.stringify(row)) even
17
+ * winds up converting DecimalBigNums to strings instead of numbers.
18
+ * For some reason a custom replacer only sees DecimalBigNums as
19
+ * strings, as well.
20
+ */
21
+ export declare const unwrapRow: (row: StructRow) => QueryDataRow;
4
22
  type RemoteFileCallback = (tableName: string) => Promise<Uint8Array | undefined>;
5
23
  export interface DuckDBWasmOptions extends ConnectionConfig {
24
+ additionalExtensions?: string[];
6
25
  databasePath?: string;
26
+ motherDuckToken: string | undefined;
7
27
  workingDirectory?: string;
8
28
  }
9
29
  export declare abstract class DuckDBWASMConnection extends DuckDBCommon {
10
30
  readonly arg: string | DuckDBWasmOptions;
31
+ private additionalExtensions;
11
32
  readonly name: string;
12
33
  private databasePath;
13
34
  protected workingDirectory: string;
@@ -20,7 +41,7 @@ export declare abstract class DuckDBWASMConnection extends DuckDBCommon {
20
41
  private remoteFileStatus;
21
42
  constructor(options: DuckDBWasmOptions, queryOptions?: QueryOptionsReader);
22
43
  constructor(name: string, databasePath?: string | null, workingDirectory?: string, queryOptions?: QueryOptionsReader);
23
- private init;
44
+ protected init(): Promise<void>;
24
45
  abstract getBundles(): duckdb.DuckDBBundles;
25
46
  get connection(): duckdb.AsyncDuckDBConnection | null;
26
47
  get database(): duckdb.AsyncDuckDB | null;
@@ -48,13 +48,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
48
48
  return (mod && mod.__esModule) ? mod : { "default": mod };
49
49
  };
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.DuckDBWASMConnection = void 0;
52
- const duckdb = __importStar(require("@malloydata/duckdb-wasm"));
51
+ exports.DuckDBWASMConnection = exports.unwrapRow = exports.unwrapArrow = void 0;
52
+ const duckdb = __importStar(require("@duckdb/duckdb-wasm"));
53
53
  const web_worker_1 = __importDefault(require("web-worker"));
54
54
  const apache_arrow_1 = require("apache-arrow");
55
55
  const duckdb_common_1 = require("./duckdb_common");
56
56
  const TABLE_MATCH = /FROM\s*('([^']*)'|"([^"]*)")/gi;
57
57
  const TABLE_FUNCTION_MATCH = /FROM\s+[a-z0-9_]+\(('([^']*)'|"([^"]*)")/gi;
58
+ const FILE_EXTS = ['.csv', '.tsv', '.parquet'];
58
59
  /**
59
60
  * Arrow's toJSON() doesn't really do what I'd expect, since
60
61
  * it still includes Arrow objects like DecimalBigNums and Vectors,
@@ -69,7 +70,7 @@ const unwrapArrow = (value) => {
69
70
  return value;
70
71
  }
71
72
  else if (value instanceof apache_arrow_1.Vector) {
72
- return [...value].map(unwrapArrow);
73
+ return [...value].map(exports.unwrapArrow);
73
74
  }
74
75
  else if (value instanceof Date) {
75
76
  return value;
@@ -88,14 +89,22 @@ const unwrapArrow = (value) => {
88
89
  return Number(obj[Symbol.toPrimitive]());
89
90
  }
90
91
  else if (Array.isArray(value)) {
91
- return value.map(unwrapArrow);
92
+ return value.map(exports.unwrapArrow);
93
+ }
94
+ else if (obj['microseconds'] && obj['timezone'] === null) {
95
+ // Convert epoch µs to ms
96
+ return Number(obj['microseconds']) / 1000;
97
+ }
98
+ else if (obj['days']) {
99
+ // Convert epoch day to Date
100
+ return new Date(obj['days'] * 8.64e7);
92
101
  }
93
102
  else {
94
103
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
104
  const result = {};
96
105
  for (const key in obj) {
97
106
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
98
- result[key] = unwrapArrow(obj[key]);
107
+ result[key] = (0, exports.unwrapArrow)(obj[key]);
99
108
  }
100
109
  }
101
110
  return result;
@@ -103,6 +112,7 @@ const unwrapArrow = (value) => {
103
112
  }
104
113
  return value;
105
114
  };
115
+ exports.unwrapArrow = unwrapArrow;
106
116
  /**
107
117
  * Process a single Arrow result row into a Malloy QueryDataRow
108
118
  * Unfortunately simply calling JSONParse(JSON.stringify(row)) even
@@ -111,19 +121,22 @@ const unwrapArrow = (value) => {
111
121
  * strings, as well.
112
122
  */
113
123
  const unwrapRow = (row) => {
114
- return unwrapArrow(row.toJSON());
124
+ return (0, exports.unwrapArrow)(row.toJSON());
115
125
  };
126
+ exports.unwrapRow = unwrapRow;
116
127
  /**
117
128
  * Process a duckedb Table into an array of Malloy QueryDataRows
118
129
  */
119
130
  const unwrapTable = (table) => {
120
- return table.toArray().map(unwrapRow);
131
+ return table.toArray().map(exports.unwrapRow);
121
132
  };
122
133
  const isNode = () => typeof navigator === 'undefined';
123
134
  class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
124
135
  constructor(arg, arg2, workingDirectory, queryOptions) {
136
+ var _a, _b;
125
137
  super();
126
138
  this.arg = arg;
139
+ this.additionalExtensions = [];
127
140
  this.databasePath = null;
128
141
  this.workingDirectory = '/';
129
142
  this._connection = null;
@@ -154,7 +167,17 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
154
167
  if (typeof arg.workingDirectory === 'string') {
155
168
  this.workingDirectory = arg.workingDirectory;
156
169
  }
170
+ if (typeof arg.motherDuckToken === 'string') {
171
+ this.motherDuckToken = arg.motherDuckToken;
172
+ }
173
+ if (Array.isArray(arg.additionalExtensions)) {
174
+ this.additionalExtensions = arg.additionalExtensions;
175
+ }
157
176
  }
177
+ this.isMotherDuck =
178
+ ((_a = this.databasePath) === null || _a === void 0 ? void 0 : _a.startsWith('md:')) ||
179
+ ((_b = this.databasePath) === null || _b === void 0 ? void 0 : _b.startsWith('motherduck:')) ||
180
+ false;
158
181
  this.connecting = this.init();
159
182
  }
160
183
  async init() {
@@ -204,10 +227,10 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
204
227
  if (this.workingDirectory) {
205
228
  await this.runDuckDBQuery(`SET FILE_SEARCH_PATH='${this.workingDirectory}'`);
206
229
  }
207
- // Not quite ready for prime time
208
- // for (const ext of ['json', 'httpfs', 'icu']) {
209
- // await this.loadExtension(ext);
210
- // }
230
+ const extensions = ['json', 'icu', ...this.additionalExtensions];
231
+ for (const ext of extensions) {
232
+ await this.loadExtension(ext);
233
+ }
211
234
  const setupCmds = ["SET TimeZone='UTC'"];
212
235
  for (const cmd of setupCmds) {
213
236
  try {
@@ -279,7 +302,7 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
279
302
  (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted)) {
280
303
  break;
281
304
  }
282
- yield unwrapRow(row);
305
+ yield (0, exports.unwrapRow)(row);
283
306
  index++;
284
307
  }
285
308
  }
@@ -299,6 +322,11 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
299
322
  };
300
323
  await this.setup();
301
324
  for (const tablePath of tables) {
325
+ if (this.isMotherDuck &&
326
+ !tables.includes('/') &&
327
+ !FILE_EXTS.some(ext => tablePath.endsWith(ext))) {
328
+ continue;
329
+ }
302
330
  // http and s3 urls are handled by duckdb-wasm
303
331
  if (tablePath.match(/^https?:\/\//)) {
304
332
  continue;
@@ -1,6 +1,15 @@
1
- import * as duckdb from '@malloydata/duckdb-wasm';
1
+ import * as duckdb from '@duckdb/duckdb-wasm';
2
2
  import { DuckDBWASMConnection as DuckDBWASMConnectionBase } from './duckdb_wasm_connection';
3
+ import { MDConnection } from '@motherduck/wasm-client';
4
+ import { QueryDataRow } from '@malloydata/malloy';
3
5
  export declare class DuckDBWASMConnection extends DuckDBWASMConnectionBase {
6
+ protected _mdConnection: MDConnection | null;
4
7
  getBundles(): duckdb.DuckDBBundles;
8
+ init(): Promise<void>;
9
+ setup(): Promise<void>;
10
+ protected runDuckDBQuery(sql: string, abortSignal?: AbortSignal): Promise<{
11
+ rows: QueryDataRow[];
12
+ totalRows: number;
13
+ }>;
5
14
  createHash(sqlCommand: string): Promise<string>;
6
15
  }
@@ -46,12 +46,89 @@ var __importStar = (this && this.__importStar) || function (mod) {
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
48
  exports.DuckDBWASMConnection = void 0;
49
- const duckdb = __importStar(require("@malloydata/duckdb-wasm"));
49
+ const duckdb = __importStar(require("@duckdb/duckdb-wasm"));
50
50
  const duckdb_wasm_connection_1 = require("./duckdb_wasm_connection");
51
+ const wasm_client_1 = require("@motherduck/wasm-client");
51
52
  class DuckDBWASMConnection extends duckdb_wasm_connection_1.DuckDBWASMConnection {
53
+ constructor() {
54
+ super(...arguments);
55
+ this._mdConnection = null;
56
+ }
52
57
  getBundles() {
53
58
  return duckdb.getJsDelivrBundles();
54
59
  }
60
+ async init() {
61
+ if (this.isMotherDuck) {
62
+ if (!this.motherDuckToken) {
63
+ throw new Error('Please set your MotherDuck token');
64
+ }
65
+ const mdConnection = wasm_client_1.MDConnection.create({
66
+ mdToken: this.motherDuckToken,
67
+ });
68
+ await mdConnection.isInitialized();
69
+ this._mdConnection = mdConnection;
70
+ console.info('MotherDuck initialized');
71
+ }
72
+ else {
73
+ await super.init();
74
+ }
75
+ }
76
+ async setup() {
77
+ if (this.isMotherDuck) {
78
+ const doSetup = async () => {
79
+ const setupCmds = ["SET TimeZone='UTC'"];
80
+ for (const cmd of setupCmds) {
81
+ try {
82
+ await this.runDuckDBQuery(cmd);
83
+ }
84
+ catch (error) {
85
+ // eslint-disable-next-line no-console
86
+ console.error(`duckdb setup ${cmd} => ${error}`);
87
+ }
88
+ }
89
+ };
90
+ await this.connecting;
91
+ if (!this.isSetup) {
92
+ this.isSetup = doSetup();
93
+ }
94
+ await this.isSetup;
95
+ }
96
+ else {
97
+ await super.setup();
98
+ }
99
+ }
100
+ async runDuckDBQuery(sql, abortSignal) {
101
+ if (this.isMotherDuck) {
102
+ if (this._mdConnection) {
103
+ const connection = this._mdConnection;
104
+ let queryId = undefined;
105
+ const cancel = () => {
106
+ if (queryId) {
107
+ connection.cancelQuery(queryId, 'Cancelled');
108
+ }
109
+ };
110
+ abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', cancel);
111
+ queryId = connection.enqueueQuery(sql);
112
+ if (queryId) {
113
+ const result = await connection.evaluateQueuedQuery(queryId);
114
+ if (result === null || result === void 0 ? void 0 : result.data) {
115
+ const rows = (0, duckdb_wasm_connection_1.unwrapArrow)(result.data.toRows());
116
+ const totalRows = result.data.rowCount;
117
+ return {
118
+ rows,
119
+ totalRows,
120
+ };
121
+ }
122
+ throw new Error('No data');
123
+ }
124
+ throw new Error('Failed to enqueue query');
125
+ }
126
+ throw new Error('MotherDuck not initialized');
127
+ }
128
+ else {
129
+ return super.runDuckDBQuery(sql, abortSignal);
130
+ }
131
+ }
55
132
  async createHash(sqlCommand) {
56
133
  const msgUint8 = new TextEncoder().encode(sqlCommand);
57
134
  const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);
@@ -1,4 +1,4 @@
1
- import { DuckDBBundles } from '@malloydata/duckdb-wasm';
1
+ import { 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;
@@ -30,13 +30,13 @@ const crypto_1 = __importDefault(require("crypto"));
30
30
  const duckdb_wasm_connection_1 = require("./duckdb_wasm_connection");
31
31
  class DuckDBWASMConnection extends duckdb_wasm_connection_1.DuckDBWASMConnection {
32
32
  getBundles() {
33
- const resolvePath = require.resolve('@malloydata/duckdb-wasm');
33
+ const resolvePath = require.resolve('@duckdb/duckdb-wasm');
34
34
  if (!resolvePath) {
35
- throw new Error('Unable to resolve @malloydata/duckdb-wasm path');
35
+ throw new Error('Unable to resolve @duckdb/duckdb-wasm path');
36
36
  }
37
37
  const distMatch = resolvePath.match(/^.*\/dist\//);
38
38
  if (!distMatch) {
39
- throw new Error('Unable to resolve @malloydata/duckdb-wasm dist path');
39
+ throw new Error('Unable to resolve @duckdb/duckdb-wasm dist path');
40
40
  }
41
41
  const dist = distMatch[0];
42
42
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/db-duckdb",
3
- "version": "0.0.130-dev240311192728",
3
+ "version": "0.0.130-dev240312011825",
4
4
  "license": "MIT",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -40,9 +40,10 @@
40
40
  "prepublishOnly": "npm run build"
41
41
  },
42
42
  "dependencies": {
43
- "@malloydata/duckdb-wasm": "0.0.6",
44
- "@malloydata/malloy": "^0.0.130-dev240311192728",
45
- "apache-arrow": "^13.0.0",
43
+ "@duckdb/duckdb-wasm": "1.28.1-dev106.0",
44
+ "@malloydata/malloy": "^0.0.130-dev240312011825",
45
+ "@motherduck/wasm-client": "^0.4.0",
46
+ "apache-arrow": "^14.0.0",
46
47
  "duckdb": "0.9.2",
47
48
  "web-worker": "^1.2.0"
48
49
  }