@rip-lang/db 0.10.0 → 1.0.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.
- package/INTERNALS.md +324 -0
- package/README.md +93 -237
- package/bin/rip-db +3 -3
- package/build.zig +88 -0
- package/db.rip +66 -80
- package/lib/darwin-arm64/duckdb.node +0 -0
- package/lib/duckdb.mjs +246 -333
- package/package.json +11 -5
- package/src/duckdb.zig +1156 -0
- package/PROTOCOL.md +0 -258
- package/db.html +0 -122
- package/lib/duckdb-binary.rip +0 -525
package/lib/duckdb.mjs
CHANGED
|
@@ -1,31 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DuckDB
|
|
2
|
+
* DuckDB High-Performance Zig Bindings
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Minimal JavaScript wrapper for the Zig implementation.
|
|
5
|
+
* The Zig code handles all binary serialization directly - no JS overhead!
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { open, queryBinary, tokenize } from './duckdb.mjs';
|
|
9
|
+
*
|
|
10
|
+
* const db = open(':memory:');
|
|
11
|
+
* const conn = db.connect();
|
|
12
|
+
*
|
|
13
|
+
* // Returns binary ArrayBuffer - ready for HTTP response!
|
|
14
|
+
* const result = queryBinary(conn.handle, 'SELECT 42', 10000);
|
|
15
|
+
*
|
|
16
|
+
* // Returns binary ArrayBuffer for tokenize endpoint
|
|
17
|
+
* const tokens = tokenize('SELECT * FROM users');
|
|
5
18
|
*/
|
|
6
19
|
|
|
7
|
-
import { dlopen, ptr
|
|
20
|
+
import { dlopen, ptr } from 'bun:ffi';
|
|
8
21
|
import { fileURLToPath } from 'url';
|
|
9
22
|
import { dirname, join } from 'path';
|
|
10
23
|
import { platform, arch } from 'process';
|
|
11
24
|
|
|
12
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
26
|
|
|
14
|
-
// Map
|
|
27
|
+
// Map platform/arch to library path
|
|
15
28
|
const archMap = { 'arm64': 'arm64', 'x64': 'x64', 'x86_64': 'x64' };
|
|
16
29
|
const target = `${platform}-${archMap[arch] || arch}`;
|
|
17
|
-
|
|
18
|
-
// Load the platform-specific native module
|
|
19
30
|
const libPath = join(__dirname, `${target}/duckdb.node`);
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
// Load the Zig library
|
|
33
|
+
const lib = dlopen(libPath, {
|
|
34
|
+
// Database lifecycle
|
|
23
35
|
duck_open: { args: ['ptr'], returns: 'usize' },
|
|
24
36
|
duck_close: { args: ['usize'], returns: 'void' },
|
|
25
37
|
duck_connect: { args: ['usize'], returns: 'usize' },
|
|
26
38
|
duck_disconnect: { args: ['usize'], returns: 'void' },
|
|
27
39
|
|
|
28
|
-
//
|
|
40
|
+
// Binary serialization (for DuckDB UI protocol)
|
|
41
|
+
duck_query_binary: { args: ['usize', 'ptr', 'ptr', 'usize', 'u64'], returns: 'usize' },
|
|
42
|
+
duck_tokenize: { args: ['ptr', 'usize', 'ptr', 'usize'], returns: 'usize' },
|
|
43
|
+
duck_empty_result: { args: ['ptr', 'usize'], returns: 'usize' },
|
|
44
|
+
duck_error_result: { args: ['ptr', 'ptr', 'usize'], returns: 'usize' },
|
|
45
|
+
|
|
46
|
+
// Basic query API (for JSON endpoints)
|
|
29
47
|
duck_query: { args: ['usize', 'ptr'], returns: 'usize' },
|
|
30
48
|
duck_free_result: { args: ['usize'], returns: 'void' },
|
|
31
49
|
duck_result_error: { args: ['usize'], returns: 'ptr' },
|
|
@@ -33,380 +51,275 @@ const duck = dlopen(libPath, {
|
|
|
33
51
|
duck_column_count: { args: ['usize'], returns: 'u64' },
|
|
34
52
|
duck_column_name: { args: ['usize', 'u64'], returns: 'ptr' },
|
|
35
53
|
duck_column_type: { args: ['usize', 'u64'], returns: 'u32' },
|
|
36
|
-
|
|
37
|
-
// Value extraction
|
|
38
54
|
duck_value_is_null: { args: ['usize', 'u64', 'u64'], returns: 'bool' },
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
duck_value_i64: { args: ['usize', 'u64', 'u64'], returns: 'i64' },
|
|
44
|
-
duck_value_u8: { args: ['usize', 'u64', 'u64'], returns: 'u8' },
|
|
45
|
-
duck_value_u16: { args: ['usize', 'u64', 'u64'], returns: 'u16' },
|
|
46
|
-
duck_value_u32: { args: ['usize', 'u64', 'u64'], returns: 'u32' },
|
|
47
|
-
duck_value_u64: { args: ['usize', 'u64', 'u64'], returns: 'u64' },
|
|
48
|
-
duck_value_f32: { args: ['usize', 'u64', 'u64'], returns: 'f32' },
|
|
49
|
-
duck_value_f64: { args: ['usize', 'u64', 'u64'], returns: 'f64' },
|
|
50
|
-
duck_value_string_internal: { args: ['usize', 'u64', 'u64'], returns: 'ptr' },
|
|
51
|
-
duck_value_date_days: { args: ['usize', 'u64', 'u64'], returns: 'i32' },
|
|
52
|
-
duck_value_time_micros: { args: ['usize', 'u64', 'u64'], returns: 'i64' },
|
|
53
|
-
duck_value_timestamp_micros: { args: ['usize', 'u64', 'u64'], returns: 'i64' },
|
|
54
|
-
|
|
55
|
-
// Prepared statements
|
|
56
|
-
duck_prepare: { args: ['usize', 'ptr'], returns: 'usize' },
|
|
57
|
-
duck_prepare_error: { args: ['usize'], returns: 'ptr' },
|
|
58
|
-
duck_free_prepare: { args: ['usize'], returns: 'void' },
|
|
59
|
-
duck_nparams: { args: ['usize'], returns: 'u64' },
|
|
60
|
-
duck_param_type: { args: ['usize', 'u64'], returns: 'u32' },
|
|
61
|
-
duck_execute_prepared: { args: ['usize'], returns: 'usize' },
|
|
62
|
-
|
|
63
|
-
// Parameter binding
|
|
64
|
-
duck_bind_null: { args: ['usize', 'u64'], returns: 'bool' },
|
|
65
|
-
duck_bind_bool: { args: ['usize', 'u64', 'bool'], returns: 'bool' },
|
|
66
|
-
duck_bind_i8: { args: ['usize', 'u64', 'i8'], returns: 'bool' },
|
|
67
|
-
duck_bind_i16: { args: ['usize', 'u64', 'i16'], returns: 'bool' },
|
|
68
|
-
duck_bind_i32: { args: ['usize', 'u64', 'i32'], returns: 'bool' },
|
|
69
|
-
duck_bind_i64: { args: ['usize', 'u64', 'i64'], returns: 'bool' },
|
|
70
|
-
duck_bind_u8: { args: ['usize', 'u64', 'u8'], returns: 'bool' },
|
|
71
|
-
duck_bind_u16: { args: ['usize', 'u64', 'u16'], returns: 'bool' },
|
|
72
|
-
duck_bind_u32: { args: ['usize', 'u64', 'u32'], returns: 'bool' },
|
|
73
|
-
duck_bind_u64: { args: ['usize', 'u64', 'u64'], returns: 'bool' },
|
|
74
|
-
duck_bind_f32: { args: ['usize', 'u64', 'f32'], returns: 'bool' },
|
|
75
|
-
duck_bind_f64: { args: ['usize', 'u64', 'f64'], returns: 'bool' },
|
|
76
|
-
duck_bind_string: { args: ['usize', 'u64', 'ptr', 'u64'], returns: 'bool' },
|
|
77
|
-
duck_bind_blob: { args: ['usize', 'u64', 'ptr', 'u64'], returns: 'bool' },
|
|
78
|
-
duck_bind_timestamp: { args: ['usize', 'u64', 'i64'], returns: 'bool' },
|
|
79
|
-
duck_bind_date: { args: ['usize', 'u64', 'i32'], returns: 'bool' },
|
|
80
|
-
duck_bind_time: { args: ['usize', 'u64', 'i64'], returns: 'bool' },
|
|
81
|
-
|
|
82
|
-
// Utility
|
|
55
|
+
duck_value_varchar: { args: ['usize', 'u64', 'u64'], returns: 'ptr' },
|
|
56
|
+
duck_value_int64: { args: ['usize', 'u64', 'u64'], returns: 'i64' },
|
|
57
|
+
duck_value_double: { args: ['usize', 'u64', 'u64'], returns: 'f64' },
|
|
58
|
+
duck_value_boolean: { args: ['usize', 'u64', 'u64'], returns: 'bool' },
|
|
83
59
|
duck_free: { args: ['usize'], returns: 'void' },
|
|
84
60
|
}).symbols;
|
|
85
61
|
|
|
86
|
-
|
|
87
|
-
// which can cause precision loss for pointer values on 64-bit systems.
|
|
88
|
-
|
|
89
|
-
const utf8 = new TextEncoder();
|
|
90
|
-
|
|
91
|
-
// DuckDB type enum
|
|
92
|
-
const Type = {
|
|
93
|
-
INVALID: 0,
|
|
94
|
-
BOOLEAN: 1,
|
|
95
|
-
TINYINT: 2,
|
|
96
|
-
SMALLINT: 3,
|
|
97
|
-
INTEGER: 4,
|
|
98
|
-
BIGINT: 5,
|
|
99
|
-
UTINYINT: 6,
|
|
100
|
-
USMALLINT: 7,
|
|
101
|
-
UINTEGER: 8,
|
|
102
|
-
UBIGINT: 9,
|
|
103
|
-
FLOAT: 10,
|
|
104
|
-
DOUBLE: 11,
|
|
105
|
-
TIMESTAMP: 12,
|
|
106
|
-
DATE: 13,
|
|
107
|
-
TIME: 14,
|
|
108
|
-
INTERVAL: 15,
|
|
109
|
-
HUGEINT: 16,
|
|
110
|
-
VARCHAR: 17,
|
|
111
|
-
BLOB: 18,
|
|
112
|
-
DECIMAL: 19,
|
|
113
|
-
TIMESTAMP_S: 20,
|
|
114
|
-
TIMESTAMP_MS: 21,
|
|
115
|
-
TIMESTAMP_NS: 22,
|
|
116
|
-
ENUM: 23,
|
|
117
|
-
LIST: 24,
|
|
118
|
-
STRUCT: 25,
|
|
119
|
-
MAP: 26,
|
|
120
|
-
UUID: 27,
|
|
121
|
-
JSON: 28,
|
|
122
|
-
};
|
|
62
|
+
import { CString } from 'bun:ffi';
|
|
123
63
|
|
|
124
|
-
//
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
[Type.TINYINT]: (r, c, row) => duck.duck_value_i8(r, c, row),
|
|
128
|
-
[Type.SMALLINT]: (r, c, row) => duck.duck_value_i16(r, c, row),
|
|
129
|
-
[Type.INTEGER]: (r, c, row) => duck.duck_value_i32(r, c, row),
|
|
130
|
-
[Type.BIGINT]: (r, c, row) => Number(duck.duck_value_i64(r, c, row)), // Convert to Number for JSON compatibility
|
|
131
|
-
[Type.UTINYINT]: (r, c, row) => duck.duck_value_u8(r, c, row),
|
|
132
|
-
[Type.USMALLINT]: (r, c, row) => duck.duck_value_u16(r, c, row),
|
|
133
|
-
[Type.UINTEGER]: (r, c, row) => duck.duck_value_u32(r, c, row),
|
|
134
|
-
[Type.UBIGINT]: (r, c, row) => Number(duck.duck_value_u64(r, c, row)), // Convert to Number for JSON compatibility
|
|
135
|
-
[Type.FLOAT]: (r, c, row) => duck.duck_value_f32(r, c, row),
|
|
136
|
-
[Type.DOUBLE]: (r, c, row) => duck.duck_value_f64(r, c, row),
|
|
137
|
-
[Type.DECIMAL]: (r, c, row) => duck.duck_value_f64(r, c, row), // Treat as double
|
|
138
|
-
[Type.VARCHAR]: (r, c, row) => new CString(duck.duck_value_string_internal(r, c, row)).toString(),
|
|
139
|
-
[Type.JSON]: (r, c, row) => new CString(duck.duck_value_string_internal(r, c, row)).toString(),
|
|
140
|
-
[Type.UUID]: (r, c, row) => new CString(duck.duck_value_string_internal(r, c, row)).toString(),
|
|
141
|
-
[Type.DATE]: (r, c, row) => duck.duck_value_date_days(r, c, row) * 86400000, // ms since epoch
|
|
142
|
-
[Type.TIME]: (r, c, row) => Number(duck.duck_value_time_micros(r, c, row)) / 1000, // ms
|
|
143
|
-
[Type.TIMESTAMP]: (r, c, row) => Number(duck.duck_value_timestamp_micros(r, c, row)) / 1000,
|
|
144
|
-
[Type.TIMESTAMP_S]: (r, c, row) => Number(duck.duck_value_timestamp_micros(r, c, row)) / 1000,
|
|
145
|
-
[Type.TIMESTAMP_MS]: (r, c, row) => Number(duck.duck_value_timestamp_micros(r, c, row)) / 1000,
|
|
146
|
-
[Type.TIMESTAMP_NS]: (r, c, row) => Number(duck.duck_value_timestamp_micros(r, c, row)) / 1000,
|
|
147
|
-
[Type.HUGEINT]: (r, c, row) => Number(duck.duck_value_i64(r, c, row)), // Simplified, Number for JSON
|
|
148
|
-
};
|
|
64
|
+
// Pre-allocated buffer for query results (16MB default)
|
|
65
|
+
const DEFAULT_BUFFER_SIZE = 16 * 1024 * 1024;
|
|
66
|
+
let sharedBuffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
|
|
149
67
|
|
|
150
|
-
|
|
151
|
-
const binders = {
|
|
152
|
-
[Type.BOOLEAN]: (stmt, idx, val) => duck.duck_bind_bool(stmt, idx, val),
|
|
153
|
-
[Type.TINYINT]: (stmt, idx, val) => duck.duck_bind_i8(stmt, idx, val | 0),
|
|
154
|
-
[Type.SMALLINT]: (stmt, idx, val) => duck.duck_bind_i16(stmt, idx, val | 0),
|
|
155
|
-
[Type.INTEGER]: (stmt, idx, val) => duck.duck_bind_i32(stmt, idx, val | 0),
|
|
156
|
-
[Type.BIGINT]: (stmt, idx, val) => duck.duck_bind_i64(stmt, idx, BigInt(val)),
|
|
157
|
-
[Type.UTINYINT]: (stmt, idx, val) => duck.duck_bind_u8(stmt, idx, val >>> 0),
|
|
158
|
-
[Type.USMALLINT]: (stmt, idx, val) => duck.duck_bind_u16(stmt, idx, val >>> 0),
|
|
159
|
-
[Type.UINTEGER]: (stmt, idx, val) => duck.duck_bind_u32(stmt, idx, val >>> 0),
|
|
160
|
-
[Type.UBIGINT]: (stmt, idx, val) => duck.duck_bind_u64(stmt, idx, BigInt(val)),
|
|
161
|
-
[Type.FLOAT]: (stmt, idx, val) => duck.duck_bind_f32(stmt, idx, val),
|
|
162
|
-
[Type.DOUBLE]: (stmt, idx, val) => duck.duck_bind_f64(stmt, idx, val),
|
|
163
|
-
[Type.DECIMAL]: (stmt, idx, val) => duck.duck_bind_f64(stmt, idx, val),
|
|
164
|
-
[Type.VARCHAR]: (stmt, idx, val) => {
|
|
165
|
-
const bytes = utf8.encode(String(val));
|
|
166
|
-
return duck.duck_bind_string(stmt, idx, ptr(bytes), bytes.length);
|
|
167
|
-
},
|
|
168
|
-
[Type.JSON]: (stmt, idx, val) => {
|
|
169
|
-
const str = typeof val === 'string' ? val : JSON.stringify(val);
|
|
170
|
-
const bytes = utf8.encode(str);
|
|
171
|
-
return duck.duck_bind_string(stmt, idx, ptr(bytes), bytes.length);
|
|
172
|
-
},
|
|
173
|
-
[Type.UUID]: (stmt, idx, val) => {
|
|
174
|
-
const bytes = utf8.encode(String(val));
|
|
175
|
-
return duck.duck_bind_string(stmt, idx, ptr(bytes), bytes.length);
|
|
176
|
-
},
|
|
177
|
-
[Type.TIMESTAMP]: (stmt, idx, val) => duck.duck_bind_timestamp(stmt, idx, BigInt(val) * 1000n),
|
|
178
|
-
[Type.DATE]: (stmt, idx, val) => duck.duck_bind_date(stmt, idx, Math.floor(val / 86400000)),
|
|
179
|
-
[Type.TIME]: (stmt, idx, val) => duck.duck_bind_time(stmt, idx, BigInt(val) * 1000n),
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// Default extractor for unknown types
|
|
183
|
-
function defaultExtractor(r, c, row) {
|
|
184
|
-
const p = duck.duck_value_string_internal(r, c, row);
|
|
185
|
-
return p ? new CString(p) : null;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Bind a value based on its JS type and param type
|
|
189
|
-
function bindValue(stmt, idx, val, paramType) {
|
|
190
|
-
if (val === null || val === undefined) {
|
|
191
|
-
return duck.duck_bind_null(stmt, idx);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const binder = binders[paramType];
|
|
195
|
-
if (binder) {
|
|
196
|
-
return binder(stmt, idx, val);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Fallback: infer from JS type
|
|
200
|
-
const t = typeof val;
|
|
201
|
-
if (t === 'boolean') return duck.duck_bind_bool(stmt, idx, val);
|
|
202
|
-
if (t === 'number') {
|
|
203
|
-
if (Number.isInteger(val)) {
|
|
204
|
-
return duck.duck_bind_i64(stmt, idx, BigInt(val));
|
|
205
|
-
}
|
|
206
|
-
return duck.duck_bind_f64(stmt, idx, val);
|
|
207
|
-
}
|
|
208
|
-
if (t === 'bigint') return duck.duck_bind_i64(stmt, idx, val);
|
|
209
|
-
if (t === 'string') {
|
|
210
|
-
const bytes = utf8.encode(val);
|
|
211
|
-
return duck.duck_bind_string(stmt, idx, ptr(bytes), bytes.length);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Object -> JSON
|
|
215
|
-
const bytes = utf8.encode(JSON.stringify(val));
|
|
216
|
-
return duck.duck_bind_string(stmt, idx, ptr(bytes), bytes.length);
|
|
217
|
-
}
|
|
68
|
+
const utf8Encoder = new TextEncoder();
|
|
218
69
|
|
|
219
70
|
/**
|
|
220
71
|
* Open a DuckDB database
|
|
221
72
|
* @param {string|null} path - Path to database file, or null/:memory: for in-memory
|
|
73
|
+
* @returns {Database}
|
|
222
74
|
*/
|
|
223
75
|
export function open(path) {
|
|
224
76
|
return new Database(path);
|
|
225
77
|
}
|
|
226
78
|
|
|
227
79
|
class Database {
|
|
228
|
-
#
|
|
80
|
+
#handle;
|
|
229
81
|
|
|
230
82
|
constructor(path) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.#
|
|
235
|
-
|
|
83
|
+
const dbPath = path === null || path === ':memory:' ? null : path;
|
|
84
|
+
const pathBytes = dbPath ? utf8Encoder.encode(dbPath + '\0') : null;
|
|
85
|
+
this.#handle = lib.duck_open(pathBytes ? ptr(pathBytes) : null);
|
|
86
|
+
if (this.#handle === 0n || this.#handle === 0) {
|
|
87
|
+
throw new Error('Failed to open database');
|
|
88
|
+
}
|
|
236
89
|
}
|
|
237
90
|
|
|
91
|
+
get handle() { return this.#handle; }
|
|
92
|
+
|
|
238
93
|
connect() {
|
|
239
|
-
return new Connection(this.#
|
|
94
|
+
return new Connection(this.#handle);
|
|
240
95
|
}
|
|
241
96
|
|
|
242
97
|
close() {
|
|
243
|
-
if (this.#
|
|
244
|
-
|
|
245
|
-
this.#
|
|
98
|
+
if (this.#handle) {
|
|
99
|
+
lib.duck_close(this.#handle);
|
|
100
|
+
this.#handle = null;
|
|
246
101
|
}
|
|
247
102
|
}
|
|
248
103
|
}
|
|
249
104
|
|
|
250
|
-
|
|
251
|
-
|
|
105
|
+
// DuckDB type constants
|
|
106
|
+
const DUCKDB_TYPE = {
|
|
107
|
+
INVALID: 0, BOOLEAN: 1, TINYINT: 2, SMALLINT: 3, INTEGER: 4, BIGINT: 5,
|
|
108
|
+
UTINYINT: 6, USMALLINT: 7, UINTEGER: 8, UBIGINT: 9, FLOAT: 10, DOUBLE: 11,
|
|
109
|
+
TIMESTAMP: 12, DATE: 13, TIME: 14, INTERVAL: 15, HUGEINT: 16, VARCHAR: 17,
|
|
110
|
+
BLOB: 18, DECIMAL: 19, TIMESTAMP_S: 20, TIMESTAMP_MS: 21, TIMESTAMP_NS: 22,
|
|
111
|
+
ENUM: 23, LIST: 24, STRUCT: 25, MAP: 26, UUID: 27,
|
|
112
|
+
};
|
|
252
113
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (this.#ptr === 0) throw new Error('Failed to connect to database');
|
|
256
|
-
}
|
|
114
|
+
class Connection {
|
|
115
|
+
#handle;
|
|
257
116
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// Check for error
|
|
263
|
-
const errPtr = duck.duck_result_error(result);
|
|
264
|
-
if (errPtr) {
|
|
265
|
-
const err = new CString(errPtr);
|
|
266
|
-
duck.duck_free_result(result);
|
|
267
|
-
throw new Error(err.toString());
|
|
117
|
+
constructor(dbHandle) {
|
|
118
|
+
this.#handle = lib.duck_connect(dbHandle);
|
|
119
|
+
if (this.#handle === 0n || this.#handle === 0) {
|
|
120
|
+
throw new Error('Failed to create connection');
|
|
268
121
|
}
|
|
122
|
+
}
|
|
269
123
|
|
|
270
|
-
|
|
271
|
-
const rowCount = Number(duck.duck_row_count(result));
|
|
272
|
-
const colCount = Number(duck.duck_column_count(result));
|
|
273
|
-
|
|
274
|
-
if (rowCount === 0) {
|
|
275
|
-
duck.duck_free_result(result);
|
|
276
|
-
return [];
|
|
277
|
-
}
|
|
124
|
+
get handle() { return this.#handle; }
|
|
278
125
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Execute SQL and return rows as array of objects
|
|
128
|
+
* @param {string} sql - SQL query
|
|
129
|
+
* @returns {object[]} Array of row objects
|
|
130
|
+
*/
|
|
131
|
+
query(sql) {
|
|
132
|
+
const sqlBytes = utf8Encoder.encode(sql + '\0');
|
|
133
|
+
const result = lib.duck_query(this.#handle, ptr(sqlBytes));
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
// Check for error
|
|
137
|
+
const errorPtr = lib.duck_result_error(result);
|
|
138
|
+
if (errorPtr) {
|
|
139
|
+
throw new Error(new CString(errorPtr).toString());
|
|
140
|
+
}
|
|
283
141
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const type = duck.duck_column_type(result, c);
|
|
287
|
-
types.push(type);
|
|
288
|
-
extract.push(extractors[type] || defaultExtractor);
|
|
289
|
-
}
|
|
142
|
+
const rowCount = Number(lib.duck_row_count(result));
|
|
143
|
+
const colCount = Number(lib.duck_column_count(result));
|
|
290
144
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const row = {};
|
|
145
|
+
// Get column names and types
|
|
146
|
+
const columns = [];
|
|
147
|
+
const types = [];
|
|
295
148
|
for (let c = 0; c < colCount; c++) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
row[columns[c]] = extract[c](result, c, r);
|
|
300
|
-
}
|
|
149
|
+
const namePtr = lib.duck_column_name(result, BigInt(c));
|
|
150
|
+
columns.push(namePtr ? new CString(namePtr).toString() : `col${c}`);
|
|
151
|
+
types.push(Number(lib.duck_column_type(result, BigInt(c))));
|
|
301
152
|
}
|
|
302
|
-
rows[r] = row;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
duck.duck_free_result(result);
|
|
306
|
-
return rows;
|
|
307
|
-
}
|
|
308
153
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
154
|
+
// Build row objects
|
|
155
|
+
const rows = [];
|
|
156
|
+
for (let r = 0; r < rowCount; r++) {
|
|
157
|
+
const row = {};
|
|
158
|
+
for (let c = 0; c < colCount; c++) {
|
|
159
|
+
const col = BigInt(c);
|
|
160
|
+
const rowIdx = BigInt(r);
|
|
161
|
+
|
|
162
|
+
if (lib.duck_value_is_null(result, col, rowIdx)) {
|
|
163
|
+
row[columns[c]] = null;
|
|
164
|
+
} else {
|
|
165
|
+
const type = types[c];
|
|
166
|
+
switch (type) {
|
|
167
|
+
case DUCKDB_TYPE.BOOLEAN:
|
|
168
|
+
row[columns[c]] = lib.duck_value_boolean(result, col, rowIdx);
|
|
169
|
+
break;
|
|
170
|
+
case DUCKDB_TYPE.TINYINT:
|
|
171
|
+
case DUCKDB_TYPE.SMALLINT:
|
|
172
|
+
case DUCKDB_TYPE.INTEGER:
|
|
173
|
+
case DUCKDB_TYPE.BIGINT:
|
|
174
|
+
case DUCKDB_TYPE.UTINYINT:
|
|
175
|
+
case DUCKDB_TYPE.USMALLINT:
|
|
176
|
+
case DUCKDB_TYPE.UINTEGER:
|
|
177
|
+
case DUCKDB_TYPE.UBIGINT:
|
|
178
|
+
case DUCKDB_TYPE.DATE:
|
|
179
|
+
case DUCKDB_TYPE.TIME:
|
|
180
|
+
case DUCKDB_TYPE.TIMESTAMP:
|
|
181
|
+
case DUCKDB_TYPE.TIMESTAMP_S:
|
|
182
|
+
case DUCKDB_TYPE.TIMESTAMP_MS:
|
|
183
|
+
case DUCKDB_TYPE.TIMESTAMP_NS:
|
|
184
|
+
row[columns[c]] = Number(lib.duck_value_int64(result, col, rowIdx));
|
|
185
|
+
break;
|
|
186
|
+
case DUCKDB_TYPE.FLOAT:
|
|
187
|
+
case DUCKDB_TYPE.DOUBLE:
|
|
188
|
+
case DUCKDB_TYPE.DECIMAL:
|
|
189
|
+
row[columns[c]] = lib.duck_value_double(result, col, rowIdx);
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
// VARCHAR, BLOB, UUID, etc - get as string
|
|
193
|
+
const strPtr = lib.duck_value_varchar(result, col, rowIdx);
|
|
194
|
+
if (strPtr) {
|
|
195
|
+
row[columns[c]] = new CString(strPtr).toString();
|
|
196
|
+
lib.duck_free(strPtr); // Free the allocated string
|
|
197
|
+
} else {
|
|
198
|
+
row[columns[c]] = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
rows.push(row);
|
|
204
|
+
}
|
|
312
205
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
duck.duck_free_prepare(stmt);
|
|
317
|
-
throw new Error(err.toString());
|
|
206
|
+
return rows;
|
|
207
|
+
} finally {
|
|
208
|
+
lib.duck_free_result(result);
|
|
318
209
|
}
|
|
319
|
-
|
|
320
|
-
return new PreparedStatement(stmt);
|
|
321
210
|
}
|
|
322
211
|
|
|
323
212
|
close() {
|
|
324
|
-
if (this.#
|
|
325
|
-
|
|
326
|
-
this.#
|
|
213
|
+
if (this.#handle) {
|
|
214
|
+
lib.duck_disconnect(this.#handle);
|
|
215
|
+
this.#handle = null;
|
|
327
216
|
}
|
|
328
217
|
}
|
|
329
218
|
}
|
|
330
219
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Execute
|
|
355
|
-
const result = duck.duck_execute_prepared(this.#ptr);
|
|
356
|
-
|
|
357
|
-
// Check for error
|
|
358
|
-
const errPtr = duck.duck_result_error(result);
|
|
359
|
-
if (errPtr) {
|
|
360
|
-
const err = new CString(errPtr);
|
|
361
|
-
duck.duck_free_result(result);
|
|
362
|
-
throw new Error(err.toString());
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Extract rows
|
|
366
|
-
const rowCount = Number(duck.duck_row_count(result));
|
|
367
|
-
const colCount = Number(duck.duck_column_count(result));
|
|
368
|
-
|
|
369
|
-
if (rowCount === 0) {
|
|
370
|
-
duck.duck_free_result(result);
|
|
371
|
-
return [];
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Get column info
|
|
375
|
-
const columns = [];
|
|
376
|
-
const types = [];
|
|
377
|
-
const extract = [];
|
|
220
|
+
/**
|
|
221
|
+
* Execute SQL and return binary result (DuckDB UI protocol format)
|
|
222
|
+
*
|
|
223
|
+
* @param {bigint|number} connHandle - Connection handle from conn.handle
|
|
224
|
+
* @param {string} sql - SQL query to execute
|
|
225
|
+
* @param {number} rowLimit - Maximum rows to return (default 10000)
|
|
226
|
+
* @returns {ArrayBuffer} - Binary result ready for HTTP response
|
|
227
|
+
*/
|
|
228
|
+
export function queryBinary(connHandle, sql, rowLimit = 10000) {
|
|
229
|
+
const sqlBytes = utf8Encoder.encode(sql + '\0');
|
|
230
|
+
const bytesWritten = lib.duck_query_binary(
|
|
231
|
+
connHandle,
|
|
232
|
+
ptr(sqlBytes),
|
|
233
|
+
ptr(sharedBuffer),
|
|
234
|
+
sharedBuffer.length,
|
|
235
|
+
BigInt(rowLimit)
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Return a copy of the used portion
|
|
239
|
+
return sharedBuffer.slice(0, Number(bytesWritten)).buffer;
|
|
240
|
+
}
|
|
378
241
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Tokenize SQL and return binary result (DuckDB UI protocol format)
|
|
244
|
+
*
|
|
245
|
+
* @param {string} sql - SQL to tokenize
|
|
246
|
+
* @returns {ArrayBuffer} - Binary result ready for HTTP response
|
|
247
|
+
*/
|
|
248
|
+
export function tokenize(sql) {
|
|
249
|
+
const sqlBytes = utf8Encoder.encode(sql);
|
|
250
|
+
const bytesWritten = lib.duck_tokenize(
|
|
251
|
+
ptr(sqlBytes),
|
|
252
|
+
sqlBytes.length,
|
|
253
|
+
ptr(sharedBuffer),
|
|
254
|
+
sharedBuffer.length
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return sharedBuffer.slice(0, Number(bytesWritten)).buffer;
|
|
258
|
+
}
|
|
385
259
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
rows[r] = row;
|
|
398
|
-
}
|
|
260
|
+
/**
|
|
261
|
+
* Create an empty binary result (for /ddb/interrupt)
|
|
262
|
+
* @returns {ArrayBuffer}
|
|
263
|
+
*/
|
|
264
|
+
export function emptyResult() {
|
|
265
|
+
const bytesWritten = lib.duck_empty_result(
|
|
266
|
+
ptr(sharedBuffer),
|
|
267
|
+
sharedBuffer.length
|
|
268
|
+
);
|
|
269
|
+
return sharedBuffer.slice(0, Number(bytesWritten)).buffer;
|
|
270
|
+
}
|
|
399
271
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Create an error binary result
|
|
274
|
+
* @param {string} message - Error message
|
|
275
|
+
* @returns {ArrayBuffer}
|
|
276
|
+
*/
|
|
277
|
+
export function errorResult(message) {
|
|
278
|
+
const msgBytes = utf8Encoder.encode(message + '\0');
|
|
279
|
+
const bytesWritten = lib.duck_error_result(
|
|
280
|
+
ptr(msgBytes),
|
|
281
|
+
ptr(sharedBuffer),
|
|
282
|
+
sharedBuffer.length
|
|
283
|
+
);
|
|
284
|
+
return sharedBuffer.slice(0, Number(bytesWritten)).buffer;
|
|
285
|
+
}
|
|
403
286
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
287
|
+
/**
|
|
288
|
+
* Resize the shared buffer (for very large results)
|
|
289
|
+
* @param {number} size - New buffer size in bytes
|
|
290
|
+
*/
|
|
291
|
+
export function setBufferSize(size) {
|
|
292
|
+
sharedBuffer = new Uint8Array(size);
|
|
410
293
|
}
|
|
411
294
|
|
|
412
|
-
|
|
295
|
+
// Export types for compatibility
|
|
296
|
+
export const Type = {
|
|
297
|
+
BOOLEAN: 10,
|
|
298
|
+
TINYINT: 11,
|
|
299
|
+
SMALLINT: 12,
|
|
300
|
+
INTEGER: 13,
|
|
301
|
+
BIGINT: 14,
|
|
302
|
+
DATE: 15,
|
|
303
|
+
TIME: 16,
|
|
304
|
+
TIMESTAMP_SEC: 17,
|
|
305
|
+
TIMESTAMP_MS: 18,
|
|
306
|
+
TIMESTAMP: 19,
|
|
307
|
+
TIMESTAMP_NS: 20,
|
|
308
|
+
DECIMAL: 21,
|
|
309
|
+
FLOAT: 22,
|
|
310
|
+
DOUBLE: 23,
|
|
311
|
+
CHAR: 24,
|
|
312
|
+
VARCHAR: 25,
|
|
313
|
+
BLOB: 26,
|
|
314
|
+
INTERVAL: 27,
|
|
315
|
+
UTINYINT: 28,
|
|
316
|
+
USMALLINT: 29,
|
|
317
|
+
UINTEGER: 30,
|
|
318
|
+
UBIGINT: 31,
|
|
319
|
+
TIMESTAMP_TZ: 32,
|
|
320
|
+
TIME_TZ: 34,
|
|
321
|
+
HUGEINT: 50,
|
|
322
|
+
UUID: 54,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export default { open, queryBinary, tokenize, emptyResult, errorResult, setBufferSize, Type };
|