@hpcc-js/wasm-duckdb 1.11.0 → 1.13.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/README.md CHANGED
@@ -1,7 +1,17 @@
1
1
  # @hpcc-js/wasm-duckdb
2
2
 
3
- This package rewraps the DuckDB webassembly distribution provided by DuckDB, this is purly a convenience to provide a consistent loading experience with the rest of the @hpcc-js.wasm library.
4
- See [DuckDB](https://github.com/duckdb/duckdb) and [DuckDB-wasm](https://github.com/duckdb/duckdb-wasm) for more details.
3
+ A WebAssembly wrapper for [DuckDB](https://github.com/duckdb/duckdb), an in-process SQL OLAP database management system. This package provides a consistent loading experience with the rest of the @hpcc-js/wasm library.
4
+
5
+ ## Features
6
+
7
+ - ✅ In-process SQL database (no server required)
8
+ - ✅ OLAP-optimized for analytical queries
9
+ - ✅ Full SQL support with DuckDB's rich feature set
10
+ - ✅ Prepared statements with parameter binding
11
+ - ✅ Transaction management (commit/rollback)
12
+ - ✅ Virtual file system for CSV, JSON, and other formats
13
+ - ✅ JSON export of query results
14
+ - ✅ Type-safe value handling
5
15
 
6
16
  ## Installation
7
17
 
@@ -9,31 +19,586 @@ See [DuckDB](https://github.com/duckdb/duckdb) and [DuckDB-wasm](https://github.
9
19
  npm install @hpcc-js/wasm-duckdb
10
20
  ```
11
21
 
12
- ## Usage
22
+ ## Quick Start
13
23
 
14
24
  ```typescript
15
25
  import { DuckDB } from "@hpcc-js/wasm-duckdb";
16
26
 
17
- let duckdb = await DuckDB.load();
18
- const c = await duckdb.db.connect();
27
+ // Load the WASM module
28
+ const duckdb = await DuckDB.load();
29
+
30
+ // Create a connection
31
+ const connection = duckdb.connect();
32
+
33
+ // Execute a query
34
+ const result = connection.query("SELECT 'Hello, DuckDB!' AS message");
35
+ console.log(result.getValue(0, 0)); // "Hello, DuckDB!"
36
+
37
+ // Clean up
38
+ result.delete();
39
+ connection.delete();
40
+ ```
41
+
42
+ ## API Reference
43
+
44
+ ### DuckDB Class
45
+
46
+ #### Static Methods
47
+
48
+ ##### `DuckDB.load(): Promise<DuckDB>`
49
+ Loads and initializes the DuckDB WASM module. Returns a singleton instance.
50
+
51
+ ```typescript
52
+ const duckdb = await DuckDB.load();
53
+ ```
54
+
55
+ ##### `DuckDB.unload(): void`
56
+ Unloads the WASM instance (useful for cleanup in tests).
57
+
58
+ #### Instance Methods
59
+
60
+ ##### `version(): string`
61
+ Returns the DuckDB version string.
62
+
63
+ ```typescript
64
+ console.log(duckdb.version()); // e.g., "v1.4.3"
65
+ ```
66
+
67
+ ##### `numberOfThreads(): number`
68
+ Returns the number of threads available.
69
+
70
+ ```typescript
71
+ console.log(duckdb.numberOfThreads()); // e.g., 1
72
+ ```
73
+
74
+ ##### `connect(): Connection`
75
+ Creates a new database connection.
76
+
77
+ ```typescript
78
+ const connection = duckdb.connect();
79
+ ```
80
+
81
+ ##### `registerFile(path: string, content: Uint8Array): void`
82
+ Registers a file in the virtual file system with binary content.
83
+
84
+ ```typescript
85
+ const csvContent = "id,name\n1,Alice\n2,Bob";
86
+ const bytes = new TextEncoder().encode(csvContent);
87
+ duckdb.registerFile("data.csv", bytes);
88
+ ```
89
+
90
+ ##### `registerFileString(fileName: string, content: string): void`
91
+ Registers a file in the virtual file system with string content.
92
+
93
+ ```typescript
94
+ const data = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
95
+ duckdb.registerFileString("data.json", JSON.stringify(data));
96
+ ```
97
+
98
+ ### Connection Class
99
+
100
+ #### Query Execution
101
+
102
+ ##### `query(sql: string): MaterializedQueryResult`
103
+ Executes a SQL query and returns the result.
104
+
105
+ ```typescript
106
+ const result = connection.query("SELECT * FROM users WHERE age > 18");
107
+ console.log(result.rowCount()); // Number of rows
108
+ result.delete(); // Always clean up!
109
+ ```
110
+
111
+ ##### `prepare(sql: string): PreparedStatement`
112
+ Creates a prepared statement for repeated execution with different parameters.
113
+
114
+ ```typescript
115
+ const stmt = connection.prepare("SELECT * FROM users WHERE id = ?");
116
+ const result = stmt.execute([42]);
117
+ result.delete();
118
+ stmt.delete();
119
+ ```
120
+
121
+ ##### `interrupt(): void`
122
+ Interrupts the currently running query.
123
+
124
+ ```typescript
125
+ connection.interrupt();
126
+ ```
127
+
128
+ ##### `getQueryProgress(): number`
129
+ Gets the progress of the currently executing query (0.0 to 1.0).
130
+
131
+ ```typescript
132
+ const progress = connection.getQueryProgress();
133
+ console.log(`Query is ${progress * 100}% complete`);
134
+ ```
135
+
136
+ #### Transaction Management
137
+
138
+ ##### `beginTransaction(): void`
139
+ Starts a new transaction.
140
+
141
+ ```typescript
142
+ connection.beginTransaction();
143
+ connection.query("INSERT INTO users VALUES (1, 'Alice')").delete();
144
+ connection.commit();
145
+ ```
19
146
 
147
+ ##### `commit(): void`
148
+ Commits the current transaction.
149
+
150
+ ##### `rollback(): void`
151
+ Rolls back the current transaction.
152
+
153
+ ```typescript
154
+ connection.beginTransaction();
155
+ connection.query("INSERT INTO users VALUES (1, 'Alice')").delete();
156
+ connection.rollback(); // Changes discarded
157
+ ```
158
+
159
+ ##### `setAutoCommit(enabled: boolean): void`
160
+ Enables or disables auto-commit mode (default: enabled).
161
+
162
+ ```typescript
163
+ connection.setAutoCommit(false);
164
+ // Multiple operations in a single transaction
165
+ connection.setAutoCommit(true);
166
+ ```
167
+
168
+ ##### `isAutoCommit(): boolean`
169
+ Checks if auto-commit is enabled.
170
+
171
+ ```typescript
172
+ console.log(connection.isAutoCommit()); // true
173
+ ```
174
+
175
+ ##### `hasActiveTransaction(): boolean`
176
+ Checks if there's an active transaction.
177
+
178
+ ```typescript
179
+ console.log(connection.hasActiveTransaction()); // false
180
+ connection.beginTransaction();
181
+ console.log(connection.hasActiveTransaction()); // true
182
+ ```
183
+
184
+ ### PreparedStatement Class
185
+
186
+ Prepared statements allow you to execute the same query multiple times with different parameters efficiently.
187
+
188
+ ##### `execute(params: any[]): QueryResult`
189
+ Executes the prepared statement with the provided parameters.
190
+
191
+ ```typescript
192
+ const stmt = connection.prepare("SELECT * FROM users WHERE age > ? AND city = ?");
193
+ const result1 = stmt.execute([18, "New York"]);
194
+ const result2 = stmt.execute([25, "London"]);
195
+
196
+ result1.delete();
197
+ result2.delete();
198
+ stmt.delete();
199
+ ```
200
+
201
+ **Supported parameter types:**
202
+ - `string` - VARCHAR
203
+ - `number` - INTEGER or DOUBLE
204
+ - `boolean` - BOOLEAN
205
+ - `null` or `undefined` - NULL
206
+
207
+ ##### `names(): string[]`
208
+ Returns the column names that will be returned by the query.
209
+
210
+ ```typescript
211
+ const stmt = connection.prepare("SELECT id, name FROM users");
212
+ console.log(stmt.names()); // ["id", "name"]
213
+ stmt.delete();
214
+ ```
215
+
216
+ ##### `types(): string[]`
217
+ Returns the column type names.
218
+
219
+ ```typescript
220
+ const stmt = connection.prepare("SELECT id, name FROM users");
221
+ console.log(stmt.types()); // ["INTEGER", "VARCHAR"]
222
+ stmt.delete();
223
+ ```
224
+
225
+ ##### `columnCount(): number`
226
+ Returns the number of columns in the result.
227
+
228
+ ```typescript
229
+ console.log(stmt.columnCount()); // 2
230
+ ```
231
+
232
+ ##### `statementType(): number`
233
+ Returns the statement type as a numeric code.
234
+
235
+ ##### `hasError(): boolean`
236
+ Checks if the statement has an error.
237
+
238
+ ```typescript
239
+ const stmt = connection.prepare("SELECT * FROM invalid_syntax WHERE");
240
+ if (stmt.hasError()) {
241
+ console.error(stmt.getError());
242
+ }
243
+ stmt.delete();
244
+ ```
245
+
246
+ ##### `getError(): string`
247
+ Returns the error message if `hasError()` is true.
248
+
249
+ ### MaterializedQueryResult Class
250
+
251
+ Represents the result of a query execution.
252
+
253
+ #### Result Metadata
254
+
255
+ ##### `rowCount(): bigint`
256
+ Returns the number of rows in the result.
257
+
258
+ ```typescript
259
+ const result = connection.query("SELECT * FROM users");
260
+ console.log(Number(result.rowCount())); // e.g., 10
261
+ result.delete();
262
+ ```
263
+
264
+ ##### `columnCount(): bigint`
265
+ Returns the number of columns in the result.
266
+
267
+ ```typescript
268
+ console.log(Number(result.columnCount())); // e.g., 3
269
+ ```
270
+
271
+ ##### `columnNames(): string[]`
272
+ Returns an array of column names.
273
+
274
+ ```typescript
275
+ const result = connection.query("SELECT id, name, age FROM users LIMIT 1");
276
+ console.log(result.columnNames()); // ["id", "name", "age"]
277
+ result.delete();
278
+ ```
279
+
280
+ ##### `columnName(index: bigint): string`
281
+ Returns the name of a specific column by index.
282
+
283
+ ```typescript
284
+ console.log(result.columnName(0n)); // "id"
285
+ console.log(result.columnName(1n)); // "name"
286
+ ```
287
+
288
+ ##### `columnTypes(): string[]`
289
+ Returns an array of column type names.
290
+
291
+ ```typescript
292
+ console.log(result.columnTypes()); // ["INTEGER", "VARCHAR", "INTEGER"]
293
+ ```
294
+
295
+ ##### `resultType(): number`
296
+ Returns the result type as a numeric code.
297
+
298
+ ##### `statementType(): number`
299
+ Returns the statement type that produced this result.
300
+
301
+ #### Data Access
302
+
303
+ ##### `getValue(column: number, row: number): any`
304
+ Returns the value at the specified column and row.
305
+
306
+ ```typescript
307
+ const result = connection.query("SELECT id, name FROM users LIMIT 1");
308
+ const id = result.getValue(0, 0); // First column, first row
309
+ const name = result.getValue(1, 0); // Second column, first row
310
+ console.log(`User ${id}: ${name}`);
311
+ result.delete();
312
+ ```
313
+
314
+ **Return types:**
315
+ - `null` for NULL values
316
+ - `boolean` for BOOLEAN columns
317
+ - `number` for numeric columns (TINYINT, SMALLINT, INTEGER, BIGINT, FLOAT, DOUBLE)
318
+ - `string` for VARCHAR and other types
319
+
320
+ ##### `toJSON(): string`
321
+ Converts the entire result to a JSON string (array of objects).
322
+
323
+ ```typescript
324
+ const result = connection.query("SELECT id, name, age FROM users");
325
+ const json = result.toJSON();
326
+ // Returns: '[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25}]'
327
+
328
+ const data = JSON.parse(json);
329
+ console.log(data[0].name); // "Alice"
330
+ result.delete();
331
+ ```
332
+
333
+ **Features:**
334
+ - Returns a JSON array with one object per row
335
+ - Column names become object keys
336
+ - NULL values are represented as `null`
337
+ - Proper JSON escaping for special characters
338
+ - NaN and Infinity are converted to `null`
339
+
340
+ ##### `collection(): ColumnDataCollection`
341
+ Returns the underlying column data collection.
342
+
343
+ ```typescript
344
+ const collection = result.collection();
345
+ console.log(Number(collection.count())); // Row count
346
+ console.log(Number(collection.columnCount())); // Column count
347
+ ```
348
+
349
+ #### Output
350
+
351
+ ##### `stringify(): string`
352
+ Returns a string representation of the result.
353
+
354
+ ```typescript
355
+ const result = connection.query("SELECT * FROM users LIMIT 3");
356
+ console.log(result.stringify());
357
+ result.delete();
358
+ ```
359
+
360
+ ##### `print(): void`
361
+ Prints the result to the console.
362
+
363
+ ```typescript
364
+ result.print();
365
+ // Outputs formatted table to console
366
+ ```
367
+
368
+ #### Error Handling
369
+
370
+ ##### `hasError(): boolean`
371
+ Checks if the query resulted in an error.
372
+
373
+ ```typescript
374
+ const result = connection.query("SELECT * FROM nonexistent_table");
375
+ if (result.hasError()) {
376
+ console.error("Query failed:", result.getError());
377
+ }
378
+ result.delete();
379
+ ```
380
+
381
+ ##### `getError(): string`
382
+ Returns the error message if one occurred.
383
+
384
+ ```typescript
385
+ const errorMsg = result.getError();
386
+ console.log(errorMsg); // Error message string
387
+ ```
388
+
389
+ ## Usage Examples
390
+
391
+ ### Basic Query
392
+
393
+ ```typescript
394
+ import { DuckDB } from "@hpcc-js/wasm-duckdb";
395
+
396
+ const duckdb = await DuckDB.load();
397
+ const connection = duckdb.connect();
398
+
399
+ const result = connection.query(`
400
+ SELECT
401
+ id,
402
+ name,
403
+ age
404
+ FROM (VALUES
405
+ (1, 'Alice', 30),
406
+ (2, 'Bob', 25),
407
+ (3, 'Charlie', 35)
408
+ ) AS users(id, name, age)
409
+ WHERE age > 25
410
+ `);
411
+
412
+ console.log(`Found ${result.rowCount()} users`);
413
+ for (let i = 0; i < Number(result.rowCount()); i++) {
414
+ const id = result.getValue(0, i);
415
+ const name = result.getValue(1, i);
416
+ const age = result.getValue(2, i);
417
+ console.log(`${id}: ${name} (${age} years old)`);
418
+ }
419
+
420
+ result.delete();
421
+ connection.delete();
422
+ ```
423
+
424
+ ### Prepared Statements
425
+
426
+ ```typescript
427
+ const connection = duckdb.connect();
428
+
429
+ // Create a table
430
+ connection.query("CREATE TABLE users (id INTEGER, name VARCHAR, age INTEGER)").delete();
431
+
432
+ // Insert data using prepared statement
433
+ const insertStmt = connection.prepare("INSERT INTO users VALUES (?, ?, ?)");
434
+ insertStmt.execute([1, "Alice", 30]).delete();
435
+ insertStmt.execute([2, "Bob", 25]).delete();
436
+ insertStmt.execute([3, "Charlie", 35]).delete();
437
+ insertStmt.delete();
438
+
439
+ // Query with prepared statement
440
+ const queryStmt = connection.prepare("SELECT * FROM users WHERE age > ?");
441
+ const result = queryStmt.execute([26]);
442
+
443
+ console.log(result.toJSON());
444
+ // [{"id":1,"name":"Alice","age":30},{"id":3,"name":"Charlie","age":35}]
445
+
446
+ result.delete();
447
+ queryStmt.delete();
448
+ connection.delete();
449
+ ```
450
+
451
+ ### Working with JSON Files
452
+
453
+ ```typescript
454
+ const duckdb = await DuckDB.load();
455
+ const connection = duckdb.connect();
456
+
457
+ // Register JSON data as a file
20
458
  const data = [
21
- { "col1": 1, "col2": "foo" },
22
- { "col1": 2, "col2": "bar" },
459
+ { product: "Laptop", price: 999.99, quantity: 5 },
460
+ { product: "Mouse", price: 29.99, quantity: 20 },
461
+ { product: "Keyboard", price: 79.99, quantity: 15 }
23
462
  ];
24
- await duckdb.db.registerFileText("rows.json", JSON.stringify(data));
25
- await c.insertJSONFromPath('rows.json', { name: 'rows' });
26
-
27
- const arrowResult = await c.query("SELECT * FROM read_json_auto('rows.json')");
28
- const result = arrowResult.toArray().map((row) => row.toJSON());
29
- expect(result.length).to.equal(data.length);
30
- for (let i = 0; i < result.length; i++) {
31
- expect(result[i].col2).to.equal(data[i].col2);
463
+ duckdb.registerFileString("products.json", JSON.stringify(data));
464
+
465
+ // Query the JSON file
466
+ const result = connection.query(`
467
+ SELECT
468
+ product,
469
+ price,
470
+ quantity,
471
+ price * quantity AS total_value
472
+ FROM read_json_auto('products.json')
473
+ ORDER BY total_value DESC
474
+ `);
475
+
476
+ console.log(result.toJSON());
477
+ result.delete();
478
+ connection.delete();
479
+ ```
480
+
481
+ ### Working with CSV Files
482
+
483
+ ```typescript
484
+ const csvData = `id,name,department,salary
485
+ 1,Alice,Engineering,120000
486
+ 2,Bob,Sales,80000
487
+ 3,Charlie,Engineering,110000
488
+ 4,Diana,HR,75000`;
489
+
490
+ duckdb.registerFileString("employees.csv", csvData);
491
+
492
+ const result = connection.query(`
493
+ SELECT
494
+ department,
495
+ COUNT(*) as employee_count,
496
+ AVG(salary) as avg_salary
497
+ FROM read_csv_auto('employees.csv')
498
+ GROUP BY department
499
+ ORDER BY avg_salary DESC
500
+ `);
501
+
502
+ result.print(); // Prints formatted table
503
+ result.delete();
504
+ ```
505
+
506
+ ### Transaction Management
507
+
508
+ ```typescript
509
+ const connection = duckdb.connect();
510
+
511
+ connection.query("CREATE TABLE accounts (id INTEGER, balance DECIMAL)").delete();
512
+ connection.query("INSERT INTO accounts VALUES (1, 1000), (2, 500)").delete();
513
+
514
+ // Transfer money between accounts
515
+ connection.setAutoCommit(false);
516
+ connection.beginTransaction();
517
+
518
+ try {
519
+ connection.query("UPDATE accounts SET balance = balance - 200 WHERE id = 1").delete();
520
+ connection.query("UPDATE accounts SET balance = balance + 200 WHERE id = 2").delete();
521
+ connection.commit();
522
+ console.log("Transfer successful");
523
+ } catch (error) {
524
+ connection.rollback();
525
+ console.error("Transfer failed, rolled back");
32
526
  }
33
527
 
34
- c.close();
528
+ connection.delete();
35
529
  ```
36
530
 
531
+ ### JSON Export
532
+
533
+ ```typescript
534
+ const result = connection.query("SELECT * FROM users ORDER BY age");
535
+
536
+ // Export entire result as JSON
537
+ const jsonString = result.toJSON();
538
+
539
+ // Save to file or send to API
540
+ console.log(jsonString);
541
+ // [{"id":2,"name":"Bob","age":25},{"id":1,"name":"Alice","age":30},...]
542
+
543
+ // Parse back to JavaScript
544
+ const users = JSON.parse(jsonString);
545
+ users.forEach(user => {
546
+ console.log(`${user.name} is ${user.age} years old`);
547
+ });
548
+
549
+ result.delete();
550
+ ```
551
+
552
+ ### Error Handling
553
+
554
+ ```typescript
555
+ const connection = duckdb.connect();
556
+
557
+ // Query error handling
558
+ const result = connection.query("SELECT * FROM nonexistent_table");
559
+ if (result.hasError()) {
560
+ console.error("Query error:", result.getError());
561
+ } else {
562
+ console.log("Query succeeded");
563
+ }
564
+ result.delete();
565
+
566
+ // Prepared statement error handling
567
+ const stmt = connection.prepare("SELECT * FROM invalid_syntax WHERE");
568
+ if (stmt.hasError()) {
569
+ console.error("Statement preparation error:", stmt.getError());
570
+ }
571
+ stmt.delete();
572
+
573
+ connection.delete();
574
+ ```
575
+
576
+ ## Memory Management
577
+
578
+ **Important:** Always call `.delete()` on results, prepared statements, and connections when done to free memory:
579
+
580
+ ```typescript
581
+ const result = connection.query("SELECT * FROM users");
582
+ // Use result...
583
+ result.delete(); // Clean up!
584
+
585
+ const stmt = connection.prepare("SELECT * FROM users WHERE id = ?");
586
+ // Use stmt...
587
+ stmt.delete(); // Clean up!
588
+
589
+ connection.delete(); // Clean up when done with connection
590
+ ```
591
+
592
+ ## TypeScript Support
593
+
594
+ This package includes full TypeScript type definitions for type-safe development.
595
+
37
596
  ## Reference
38
597
 
39
598
  * [API Documentation](https://hpcc-systems.github.io/hpcc-js-wasm/duckdb/src/duckdb/classes/DuckDB.html)
599
+ * [DuckDB Documentation](https://duckdb.org/docs/)
600
+ * [DuckDB WASM](https://github.com/duckdb/duckdb-wasm)
601
+
602
+ ## License
603
+
604
+ See the root package for license information.