@uwdata/mosaic-duckdb 0.21.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/to-arrow.js CHANGED
@@ -21,4 +21,5 @@ output.on('error', (error) => {
21
21
  });
22
22
 
23
23
  // write arrow bytes to output
24
- output.end(buf);
24
+ for (const chunk of buf) output.write(chunk);
25
+ output.end();
@@ -1,22 +1,68 @@
1
1
  export class DuckDB {
2
- constructor(path?: string, config?: {}, initStatements?: string);
3
- db: duckdb.Database;
4
- con: duckdb.Connection;
5
- _init: Promise<any>;
6
- close(): Promise<any>;
7
- prepare(sql: any): DuckDBStatement;
8
- exec(sql: any): Promise<any>;
9
- query(sql: any): Promise<any>;
10
- arrowBuffer(sql: any): Promise<any>;
2
+ /**
3
+ * @param {string} path
4
+ * @param {Record<string, string>} config
5
+ * @param {string} initStatements
6
+ */
7
+ constructor(path?: string, config?: Record<string, string>, initStatements?: string);
8
+ /** @type {Promise<void>} */
9
+ _init: Promise<void>;
10
+ /** @type {DuckDBInstance | undefined} */
11
+ db: DuckDBInstance | undefined;
12
+ /** @type {DuckDBConnection | undefined} */
13
+ con: DuckDBConnection | undefined;
14
+ /**
15
+ * @param {string} path
16
+ * @param {Record<string, string>} config
17
+ * @param {string} initStatements
18
+ */
19
+ _initialize(path: string, config: Record<string, string>, initStatements: string): Promise<void>;
20
+ close(): void;
21
+ /**
22
+ * @param {string} sql
23
+ * @returns {Promise<DuckDBStatement>}
24
+ */
25
+ prepare(sql: string): Promise<DuckDBStatement>;
26
+ /**
27
+ * @param {string | { toString(): string }} sql
28
+ * @returns {Promise<this>}
29
+ */
30
+ exec(sql: string | {
31
+ toString(): string;
32
+ }): Promise<this>;
33
+ /**
34
+ * @param {string} sql
35
+ * @returns {Promise<Record<string, Json>[]>}
36
+ */
37
+ query(sql: string): Promise<Record<string, Json>[]>;
38
+ /**
39
+ * @param {string} sql
40
+ * @returns {Promise<Uint8Array[]>}
41
+ */
42
+ arrowBuffer(sql: string): Promise<Uint8Array[]>;
11
43
  }
12
44
  export class DuckDBStatement {
13
- constructor(statement: any);
14
- statement: any;
45
+ /** @param {DuckDBPreparedStatement} statement */
46
+ constructor(statement: DuckDBPreparedStatement);
47
+ /** @type {DuckDBPreparedStatement} */
48
+ statement: DuckDBPreparedStatement;
15
49
  finalize(): void;
16
- run(params: any): void;
17
- exec(params: any): Promise<any>;
18
- query(params: any): Promise<any>;
19
- arrowBuffer(params: any): Promise<any>;
50
+ /** @param {DuckDBValue[]} [params] */
51
+ run(params?: DuckDBValue[]): Promise<void>;
52
+ /**
53
+ * @param {DuckDBValue[]} [params]
54
+ * @returns {Promise<this>}
55
+ */
56
+ exec(params?: DuckDBValue[]): Promise<this>;
57
+ /**
58
+ * @param {DuckDBValue[]} [params]
59
+ * @returns {Promise<Record<string, Json>[]>}
60
+ */
61
+ query(params?: DuckDBValue[]): Promise<Record<string, Json>[]>;
20
62
  }
21
- import duckdb from 'duckdb';
63
+ import { DuckDBInstance } from '@duckdb/node-api';
64
+ import type { DuckDBConnection } from '@duckdb/node-api';
65
+ import type { Json } from '@duckdb/node-api';
66
+ import type { DuckDBPreparedStatement } from '@duckdb/node-api';
67
+ import type { DuckDBValue } from '@duckdb/node-api';
22
68
  //# sourceMappingURL=DuckDB.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DuckDB.d.ts","sourceRoot":"","sources":["../../src/DuckDB.js"],"names":[],"mappings":"AAaA;IACE,iEASC;IAJC,oBAA2C;IAC3C,uBAA4B;IAE5B,oBAAsC;IAGxC,sBAUC;IAED,mCAEC;IAED,6BAUC;IAED,8BAUC;IAED,oCAUC;CACF;AAED;IACE,4BAEC;IADC,eAA0B;IAG5B,iBAEC;IAED,uBAEC;IAED,gCAUC;IAED,iCAUC;IAED,uCAUC;CACF;mBA9HkB,QAAQ"}
1
+ {"version":3,"file":"DuckDB.d.ts","sourceRoot":"","sources":["../../src/DuckDB.js"],"names":[],"mappings":"AAcA;IAQE;;;;OAIG;IACH,mBAJW,MAAM,WACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,mBACtB,MAAM,EAQhB;IAlBD,4BAA4B;IAC5B,OADW,OAAO,CAAC,IAAI,CAAC,CAClB;IACN,yCAAyC;IACzC,IADW,cAAc,GAAG,SAAS,CAClC;IACH,2CAA2C;IAC3C,KADW,gBAAgB,GAAG,SAAS,CACnC;IAeJ;;;;OAIG;IACH,kBAJW,MAAM,UACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,kBACtB,MAAM,iBAMhB;IAED,cAGC;IAED;;;OAGG;IACH,aAHW,MAAM,GACJ,OAAO,CAAC,eAAe,CAAC,CAMpC;IAED;;;OAGG;IACH,UAHW,MAAM,GAAG;QAAE,QAAQ,IAAI,MAAM,CAAA;KAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAMzB;IAED;;;OAGG;IACH,WAHW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAM3C;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,OAAO,CAAC,UAAU,EAAE,CAAC,CAQjC;CAEF;AAED;IAIE,iDAAiD;IACjD,uBADY,uBAAuB,EAGlC;IAND,sCAAsC;IACtC,WADW,uBAAuB,CACxB;IAOV,iBAEC;IAED,sCAAsC;IACtC,aADY,WAAW,EAAE,iBAIxB;IAED;;;OAGG;IACH,cAHW,WAAW,EAAE,GACX,OAAO,CAAC,IAAI,CAAC,CAMzB;IAED;;;OAGG;IACH,eAHW,WAAW,EAAE,GACX,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAM3C;CACF;+BArI8B,kBAAkB;sCAEiC,kBAAkB;0BAAlB,kBAAkB;6CAAlB,kBAAkB;iCAAlB,kBAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"data-server.d.ts","sourceRoot":"","sources":["../../src/data-server.js"],"names":[],"mappings":"AAOA;;;;;yEAgBC;AA6CD,yDAqBgB,QAAG,EAAE,SAAI,mBAwCxB;AAwBD;;;;;EAmBC;iBA5KgB,WAAW"}
1
+ {"version":3,"file":"data-server.d.ts","sourceRoot":"","sources":["../../src/data-server.js"],"names":[],"mappings":"AAOA;;;;;yEAgBC;AA6CD,yDAqBgB,QAAG,EAAE,SAAI,mBAwCxB;AAyBD;;;;;EAmBC;iBA7KgB,WAAW"}
@@ -1,2 +1,2 @@
1
- export function loadArrow(db: any, tableName: any, buffer: any): Promise<any>;
1
+ export function loadArrow(db: any, tableName: any, buffer: any): Promise<void>;
2
2
  //# sourceMappingURL=arrow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"arrow.d.ts","sourceRoot":"","sources":["../../../src/load/arrow.js"],"names":[],"mappings":"AAEA,8EAWC"}
1
+ {"version":3,"file":"arrow.d.ts","sourceRoot":"","sources":["../../../src/load/arrow.js"],"names":[],"mappings":"AAKA,+EAWC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwdata/mosaic-duckdb",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "A Promise-based DuckDB API and Node.js data server.",
5
5
  "keywords": [
6
6
  "duckdb",
@@ -36,12 +36,16 @@
36
36
  "lint": "eslint src test",
37
37
  "server": "node bin/run-server.js",
38
38
  "test": "vitest run",
39
- "prepublishOnly": "npm run test && npm run lint && tsc --build"
39
+ "prepublishOnly": "pnpm run test && pnpm run lint && tsc --build"
40
40
  },
41
41
  "dependencies": {
42
- "@uwdata/mosaic-sql": "^0.21.1",
43
- "duckdb": "~1.4.1",
44
- "ws": "^8.18.3"
42
+ "@duckdb/node-api": "~1.4.4-r.3",
43
+ "@uwdata/mosaic-sql": "^0.23.0",
44
+ "ws": "^8.20.0"
45
45
  },
46
- "gitHead": "296762423ac032193344646ad4f10cf604ca69a2"
46
+ "devDependencies": {
47
+ "@types/node": "^25.5.0",
48
+ "@uwdata/flechette": "^2.3.0"
49
+ },
50
+ "gitHead": "8d153a15b9b1c69130d5384ecd59aa634e3f90b3"
47
51
  }
package/src/DuckDB.js CHANGED
@@ -1,5 +1,6 @@
1
- import duckdb from 'duckdb';
2
- import { mergeBuffers } from './merge-buffers.js';
1
+ import { DuckDBInstance } from '@duckdb/node-api';
2
+
3
+ /** @import { DuckDBConnection, DuckDBPreparedStatement, DuckDBValue, Json } from '@duckdb/node-api' */
3
4
 
4
5
  const TEMP_DIR = '.duckdb';
5
6
 
@@ -12,116 +13,122 @@ const DEFAULT_INIT_STATEMENTS = [
12
13
  ].join(';\n');
13
14
 
14
15
  export class DuckDB {
16
+ /** @type {Promise<void>} */
17
+ _init;
18
+ /** @type {DuckDBInstance | undefined} */
19
+ db;
20
+ /** @type {DuckDBConnection | undefined} */
21
+ con;
22
+
23
+ /**
24
+ * @param {string} path
25
+ * @param {Record<string, string>} config
26
+ * @param {string} initStatements
27
+ */
15
28
  constructor(
16
29
  path = ':memory:',
17
30
  config = {},
18
31
  initStatements = DEFAULT_INIT_STATEMENTS
19
32
  ) {
20
- this.db = new duckdb.Database(path, config);
21
- this.con = this.db.connect();
22
- // store initialization promise so that we can wait for it
23
- this._init = this.exec(initStatements);
33
+ this._init = this._initialize(path, config, initStatements);
34
+ }
35
+
36
+ /**
37
+ * @param {string} path
38
+ * @param {Record<string, string>} config
39
+ * @param {string} initStatements
40
+ */
41
+ async _initialize(path, config, initStatements) {
42
+ this.db = await DuckDBInstance.create(path, config);
43
+ this.con = await this.db.connect();
44
+ await this.con.run(initStatements);
24
45
  }
25
46
 
26
47
  close() {
27
- return new Promise((resolve, reject) => {
28
- this.db.close((err) => {
29
- if (err) {
30
- reject(err);
31
- } else {
32
- resolve(this);
33
- }
34
- });
35
- });
48
+ this.con?.closeSync();
49
+ this.db?.closeSync();
36
50
  }
37
51
 
38
- prepare(sql) {
39
- return new DuckDBStatement(this.con.prepare(sql));
52
+ /**
53
+ * @param {string} sql
54
+ * @returns {Promise<DuckDBStatement>}
55
+ */
56
+ async prepare(sql) {
57
+ await this._init;
58
+ const stmt = await this.con.prepare(sql);
59
+ return new DuckDBStatement(stmt);
40
60
  }
41
61
 
42
- exec(sql) {
43
- return new Promise((resolve, reject) => {
44
- this.con.exec(sql, (err) => {
45
- if (err) {
46
- reject(err);
47
- } else {
48
- resolve(this);
49
- }
50
- });
51
- });
62
+ /**
63
+ * @param {string | { toString(): string }} sql
64
+ * @returns {Promise<this>}
65
+ */
66
+ async exec(sql) {
67
+ await this._init;
68
+ await this.con.run(String(sql));
69
+ return this;
52
70
  }
53
71
 
54
- query(sql) {
55
- return new Promise((resolve, reject) => {
56
- this.con.all(sql, (err, result) => {
57
- if (err) {
58
- reject(err);
59
- } else {
60
- resolve(result);
61
- }
62
- });
63
- });
72
+ /**
73
+ * @param {string} sql
74
+ * @returns {Promise<Record<string, Json>[]>}
75
+ */
76
+ async query(sql) {
77
+ await this._init;
78
+ const reader = await this.con.runAndReadAll(sql);
79
+ return reader.getRowObjectsJson();
64
80
  }
65
81
 
66
- arrowBuffer(sql) {
67
- return new Promise((resolve, reject) => {
68
- this.con.arrowIPCAll(sql, (err, result) => {
69
- if (err) {
70
- reject(err);
71
- } else {
72
- resolve(mergeBuffers(result));
73
- }
74
- });
75
- });
82
+ /**
83
+ * @param {string} sql
84
+ * @returns {Promise<Uint8Array[]>}
85
+ */
86
+ async arrowBuffer(sql) {
87
+ await this._init;
88
+ const reader = await this.con.runAndReadAll(
89
+ `SELECT * FROM to_arrow_ipc((${sql}))`
90
+ );
91
+ return /** @type {Uint8Array[]} */ (reader.getColumnsJS()[0]) ?? [];
76
92
  }
93
+
77
94
  }
78
95
 
79
96
  export class DuckDBStatement {
97
+ /** @type {DuckDBPreparedStatement} */
98
+ statement;
99
+
100
+ /** @param {DuckDBPreparedStatement} statement */
80
101
  constructor(statement) {
81
102
  this.statement = statement;
82
103
  }
83
104
 
84
105
  finalize() {
85
- this.statement.finalize();
86
- }
87
-
88
- run(params) {
89
- this.statement.run(...params);
106
+ this.statement.destroySync();
90
107
  }
91
108
 
92
- exec(params) {
93
- return new Promise((resolve, reject) => {
94
- this.statement.run(...params, (err) => {
95
- if (err) {
96
- reject(err);
97
- } else {
98
- resolve(this);
99
- }
100
- });
101
- });
109
+ /** @param {DuckDBValue[]} [params] */
110
+ async run(params) {
111
+ if (params?.length) this.statement.bind(params);
112
+ await this.statement.run();
102
113
  }
103
114
 
104
- query(params) {
105
- return new Promise((resolve, reject) => {
106
- this.statement.all(...params, (err, result) => {
107
- if (err) {
108
- reject(err);
109
- } else {
110
- resolve(result);
111
- }
112
- });
113
- });
115
+ /**
116
+ * @param {DuckDBValue[]} [params]
117
+ * @returns {Promise<this>}
118
+ */
119
+ async exec(params) {
120
+ if (params?.length) this.statement.bind(params);
121
+ await this.statement.run();
122
+ return this;
114
123
  }
115
124
 
116
- arrowBuffer(params) {
117
- return new Promise((resolve, reject) => {
118
- this.statement.arrowIPCAll(...params, (err, result) => {
119
- if (err) {
120
- reject(err);
121
- } else {
122
- resolve(mergeBuffers(result));
123
- }
124
- });
125
- });
125
+ /**
126
+ * @param {DuckDBValue[]} [params]
127
+ * @returns {Promise<Record<string, Json>[]>}
128
+ */
129
+ async query(params) {
130
+ if (params?.length) this.statement.bind(params);
131
+ const reader = await this.statement.runAndReadAll();
132
+ return reader.getRowObjectsJson();
126
133
  }
127
134
  }
@@ -133,7 +133,8 @@ function httpResponse(res) {
133
133
  return {
134
134
  arrow(data) {
135
135
  res.setHeader('Content-Type', 'application/vnd.apache.arrow.stream');
136
- res.end(data);
136
+ for (const chunk of data) res.write(chunk);
137
+ res.end();
137
138
  },
138
139
  json(data) {
139
140
  res.setHeader('Content-Type', 'application/json');
@@ -157,7 +158,7 @@ export function socketResponse(ws) {
157
158
 
158
159
  return {
159
160
  arrow(data) {
160
- ws.send(data, BINARY);
161
+ ws.send(Buffer.concat(data), BINARY);
161
162
  },
162
163
  json(data) {
163
164
  ws.send(JSON.stringify(data), STRING);
package/src/load/arrow.js CHANGED
@@ -1,14 +1,17 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { readFile, writeFile, unlink } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { randomBytes } from 'node:crypto';
2
5
 
3
6
  export async function loadArrow(db, tableName, buffer) {
4
- const arrowData = ArrayBuffer.isView(buffer) ? buffer : await readFile(buffer);
5
- return new Promise((resolve, reject) => {
6
- db.con.register_buffer(tableName, [arrowData], true, err => {
7
- if (err) {
8
- console.error(err);
9
- reject(err);
10
- }
11
- resolve();
12
- });
13
- });
7
+ const arrowData = Array.isArray(buffer) ? Buffer.concat(buffer)
8
+ : ArrayBuffer.isView(buffer) ? Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
9
+ : await readFile(buffer);
10
+ const tempFile = join(tmpdir(), `mosaic_${randomBytes(8).toString('hex')}.arrow`);
11
+ await writeFile(tempFile, arrowData);
12
+ try {
13
+ await db.exec(`CREATE OR REPLACE TABLE "${tableName}" AS SELECT * FROM '${tempFile}'`);
14
+ } finally {
15
+ await unlink(tempFile).catch(() => {});
16
+ }
14
17
  }
@@ -1,13 +0,0 @@
1
- export function mergeBuffers(buffers) {
2
- const len = buffers.reduce((a, b) => a + (b ? b.length : 0), 0);
3
- const buf = new Uint8Array(len);
4
-
5
- for (let i = 0, offset = 0; i < buffers.length; ++i) {
6
- if (buffers[i]) {
7
- buf.set(buffers[i], offset);
8
- offset += buffers[i].length;
9
- }
10
- }
11
-
12
- return buf;
13
- }