@leonardovida-md/drizzle-neo-duckdb 1.0.2 → 1.0.3
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 +36 -18
- package/dist/client.d.ts +12 -0
- package/dist/columns.d.ts +18 -10
- package/dist/driver.d.ts +4 -0
- package/dist/duckdb-introspect.mjs +171 -23
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +387 -37
- package/dist/olap.d.ts +46 -0
- package/dist/session.d.ts +5 -0
- package/dist/value-wrappers.d.ts +104 -0
- package/package.json +7 -3
- package/src/bin/duckdb-introspect.ts +12 -3
- package/src/client.ts +135 -18
- package/src/columns.ts +65 -36
- package/src/dialect.ts +2 -2
- package/src/driver.ts +20 -6
- package/src/index.ts +3 -0
- package/src/introspect.ts +15 -10
- package/src/migrator.ts +1 -3
- package/src/olap.ts +189 -0
- package/src/select-builder.ts +3 -7
- package/src/session.ts +87 -18
- package/src/sql/query-rewriters.ts +5 -8
- package/src/sql/result-mapper.ts +6 -6
- package/src/sql/selection.ts +2 -9
- package/src/value-wrappers.ts +324 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listValue,
|
|
3
|
+
arrayValue,
|
|
4
|
+
structValue,
|
|
5
|
+
mapValue,
|
|
6
|
+
blobValue,
|
|
7
|
+
timestampValue,
|
|
8
|
+
timestampTZValue,
|
|
9
|
+
type DuckDBValue,
|
|
10
|
+
type DuckDBMapEntry,
|
|
11
|
+
} from '@duckdb/node-api';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Symbol used to identify wrapped DuckDB values for native binding.
|
|
15
|
+
* Uses Symbol.for() to ensure cross-module compatibility.
|
|
16
|
+
*/
|
|
17
|
+
export const DUCKDB_VALUE_MARKER = Symbol.for('drizzle-duckdb:value');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Type identifier for each wrapper kind.
|
|
21
|
+
*/
|
|
22
|
+
export type DuckDBValueKind =
|
|
23
|
+
| 'list'
|
|
24
|
+
| 'array'
|
|
25
|
+
| 'struct'
|
|
26
|
+
| 'map'
|
|
27
|
+
| 'timestamp'
|
|
28
|
+
| 'blob'
|
|
29
|
+
| 'json';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Base interface for all tagged DuckDB value wrappers.
|
|
33
|
+
*/
|
|
34
|
+
export interface DuckDBValueWrapper<
|
|
35
|
+
TKind extends DuckDBValueKind = DuckDBValueKind,
|
|
36
|
+
TData = unknown,
|
|
37
|
+
> {
|
|
38
|
+
readonly [DUCKDB_VALUE_MARKER]: true;
|
|
39
|
+
readonly kind: TKind;
|
|
40
|
+
readonly data: TData;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* List wrapper - maps to DuckDBListValue
|
|
45
|
+
*/
|
|
46
|
+
export interface ListValueWrapper
|
|
47
|
+
extends DuckDBValueWrapper<'list', unknown[]> {
|
|
48
|
+
readonly elementType?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Array wrapper (fixed size) - maps to DuckDBArrayValue
|
|
53
|
+
*/
|
|
54
|
+
export interface ArrayValueWrapper
|
|
55
|
+
extends DuckDBValueWrapper<'array', unknown[]> {
|
|
56
|
+
readonly elementType?: string;
|
|
57
|
+
readonly fixedLength?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Struct wrapper - maps to DuckDBStructValue
|
|
62
|
+
*/
|
|
63
|
+
export interface StructValueWrapper
|
|
64
|
+
extends DuckDBValueWrapper<'struct', Record<string, unknown>> {
|
|
65
|
+
readonly schema?: Record<string, string>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Map wrapper - maps to DuckDBMapValue
|
|
70
|
+
*/
|
|
71
|
+
export interface MapValueWrapper
|
|
72
|
+
extends DuckDBValueWrapper<'map', Record<string, unknown>> {
|
|
73
|
+
readonly valueType?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Timestamp wrapper - maps to DuckDBTimestampValue or DuckDBTimestampTZValue
|
|
78
|
+
*/
|
|
79
|
+
export interface TimestampValueWrapper
|
|
80
|
+
extends DuckDBValueWrapper<'timestamp', Date | string> {
|
|
81
|
+
readonly withTimezone: boolean;
|
|
82
|
+
readonly precision?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Blob wrapper - maps to DuckDBBlobValue
|
|
87
|
+
*/
|
|
88
|
+
export interface BlobValueWrapper
|
|
89
|
+
extends DuckDBValueWrapper<'blob', Buffer | Uint8Array> {}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* JSON wrapper - delays JSON.stringify() to binding time.
|
|
93
|
+
* DuckDB stores JSON as VARCHAR internally.
|
|
94
|
+
*/
|
|
95
|
+
export interface JsonValueWrapper extends DuckDBValueWrapper<'json', unknown> {}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Union of all wrapper types for exhaustive type checking.
|
|
99
|
+
*/
|
|
100
|
+
export type AnyDuckDBValueWrapper =
|
|
101
|
+
| ListValueWrapper
|
|
102
|
+
| ArrayValueWrapper
|
|
103
|
+
| StructValueWrapper
|
|
104
|
+
| MapValueWrapper
|
|
105
|
+
| TimestampValueWrapper
|
|
106
|
+
| BlobValueWrapper
|
|
107
|
+
| JsonValueWrapper;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Type guard to check if a value is a tagged DuckDB wrapper.
|
|
111
|
+
* Optimized for fast detection in the hot path.
|
|
112
|
+
*/
|
|
113
|
+
export function isDuckDBWrapper(
|
|
114
|
+
value: unknown
|
|
115
|
+
): value is AnyDuckDBValueWrapper {
|
|
116
|
+
return (
|
|
117
|
+
value !== null &&
|
|
118
|
+
typeof value === 'object' &&
|
|
119
|
+
DUCKDB_VALUE_MARKER in value &&
|
|
120
|
+
(value as DuckDBValueWrapper)[DUCKDB_VALUE_MARKER] === true
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create a list wrapper for variable-length lists.
|
|
126
|
+
*/
|
|
127
|
+
export function wrapList(
|
|
128
|
+
data: unknown[],
|
|
129
|
+
elementType?: string
|
|
130
|
+
): ListValueWrapper {
|
|
131
|
+
return {
|
|
132
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
133
|
+
kind: 'list',
|
|
134
|
+
data,
|
|
135
|
+
elementType,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Create an array wrapper for fixed-length arrays.
|
|
141
|
+
*/
|
|
142
|
+
export function wrapArray(
|
|
143
|
+
data: unknown[],
|
|
144
|
+
elementType?: string,
|
|
145
|
+
fixedLength?: number
|
|
146
|
+
): ArrayValueWrapper {
|
|
147
|
+
return {
|
|
148
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
149
|
+
kind: 'array',
|
|
150
|
+
data,
|
|
151
|
+
elementType,
|
|
152
|
+
fixedLength,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a struct wrapper for named field structures.
|
|
158
|
+
*/
|
|
159
|
+
export function wrapStruct(
|
|
160
|
+
data: Record<string, unknown>,
|
|
161
|
+
schema?: Record<string, string>
|
|
162
|
+
): StructValueWrapper {
|
|
163
|
+
return {
|
|
164
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
165
|
+
kind: 'struct',
|
|
166
|
+
data,
|
|
167
|
+
schema,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create a map wrapper for key-value maps.
|
|
173
|
+
*/
|
|
174
|
+
export function wrapMap(
|
|
175
|
+
data: Record<string, unknown>,
|
|
176
|
+
valueType?: string
|
|
177
|
+
): MapValueWrapper {
|
|
178
|
+
return {
|
|
179
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
180
|
+
kind: 'map',
|
|
181
|
+
data,
|
|
182
|
+
valueType,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create a timestamp wrapper.
|
|
188
|
+
*/
|
|
189
|
+
export function wrapTimestamp(
|
|
190
|
+
data: Date | string,
|
|
191
|
+
withTimezone: boolean,
|
|
192
|
+
precision?: number
|
|
193
|
+
): TimestampValueWrapper {
|
|
194
|
+
return {
|
|
195
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
196
|
+
kind: 'timestamp',
|
|
197
|
+
data,
|
|
198
|
+
withTimezone,
|
|
199
|
+
precision,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a blob wrapper for binary data.
|
|
205
|
+
*/
|
|
206
|
+
export function wrapBlob(data: Buffer | Uint8Array): BlobValueWrapper {
|
|
207
|
+
return {
|
|
208
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
209
|
+
kind: 'blob',
|
|
210
|
+
data,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Create a JSON wrapper that delays JSON.stringify() to binding time.
|
|
216
|
+
* This ensures consistent handling with other wrapped types.
|
|
217
|
+
*/
|
|
218
|
+
export function wrapJson(data: unknown): JsonValueWrapper {
|
|
219
|
+
return {
|
|
220
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
221
|
+
kind: 'json',
|
|
222
|
+
data,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Convert a Date or string to microseconds since Unix epoch.
|
|
228
|
+
* Handles both Date objects and ISO-like timestamp strings.
|
|
229
|
+
*/
|
|
230
|
+
function dateToMicros(value: Date | string): bigint {
|
|
231
|
+
if (value instanceof Date) {
|
|
232
|
+
return BigInt(value.getTime()) * 1000n;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// For strings, normalize the format for reliable parsing
|
|
236
|
+
// Handle both 'YYYY-MM-DD HH:MM:SS' and 'YYYY-MM-DDTHH:MM:SS' formats
|
|
237
|
+
let normalized = value;
|
|
238
|
+
if (!value.includes('T') && value.includes(' ')) {
|
|
239
|
+
// Convert 'YYYY-MM-DD HH:MM:SS' to ISO format
|
|
240
|
+
normalized = value.replace(' ', 'T');
|
|
241
|
+
}
|
|
242
|
+
// Add 'Z' suffix if no timezone offset to treat as UTC
|
|
243
|
+
if (!normalized.endsWith('Z') && !/[+-]\d{2}:?\d{2}$/.test(normalized)) {
|
|
244
|
+
normalized += 'Z';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const date = new Date(normalized);
|
|
248
|
+
if (isNaN(date.getTime())) {
|
|
249
|
+
throw new Error(`Invalid timestamp string: ${value}`);
|
|
250
|
+
}
|
|
251
|
+
return BigInt(date.getTime()) * 1000n;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Convert Buffer or Uint8Array to Uint8Array.
|
|
256
|
+
*/
|
|
257
|
+
function toUint8Array(data: Buffer | Uint8Array): Uint8Array {
|
|
258
|
+
return data instanceof Uint8Array && !(data instanceof Buffer)
|
|
259
|
+
? data
|
|
260
|
+
: new Uint8Array(data);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Convert struct entries to DuckDB struct value entries.
|
|
265
|
+
*/
|
|
266
|
+
function convertStructEntries(
|
|
267
|
+
data: Record<string, unknown>,
|
|
268
|
+
toValue: (v: unknown) => DuckDBValue
|
|
269
|
+
): Record<string, DuckDBValue> {
|
|
270
|
+
const entries: Record<string, DuckDBValue> = {};
|
|
271
|
+
for (const [key, val] of Object.entries(data)) {
|
|
272
|
+
entries[key] = toValue(val);
|
|
273
|
+
}
|
|
274
|
+
return entries;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Convert map entries to DuckDB map entry format.
|
|
279
|
+
*/
|
|
280
|
+
function convertMapEntries(
|
|
281
|
+
data: Record<string, unknown>,
|
|
282
|
+
toValue: (v: unknown) => DuckDBValue
|
|
283
|
+
): DuckDBMapEntry[] {
|
|
284
|
+
return Object.entries(data).map(([key, val]) => ({
|
|
285
|
+
key: key as DuckDBValue,
|
|
286
|
+
value: toValue(val),
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Convert a wrapper to a DuckDB Node API value.
|
|
292
|
+
* Uses exhaustive switch for compile-time safety.
|
|
293
|
+
*/
|
|
294
|
+
export function wrapperToNodeApiValue(
|
|
295
|
+
wrapper: AnyDuckDBValueWrapper,
|
|
296
|
+
toValue: (v: unknown) => DuckDBValue
|
|
297
|
+
): DuckDBValue {
|
|
298
|
+
switch (wrapper.kind) {
|
|
299
|
+
case 'list':
|
|
300
|
+
return listValue(wrapper.data.map(toValue));
|
|
301
|
+
case 'array':
|
|
302
|
+
return arrayValue(wrapper.data.map(toValue));
|
|
303
|
+
case 'struct':
|
|
304
|
+
return structValue(convertStructEntries(wrapper.data, toValue));
|
|
305
|
+
case 'map':
|
|
306
|
+
return mapValue(convertMapEntries(wrapper.data, toValue));
|
|
307
|
+
case 'timestamp':
|
|
308
|
+
return wrapper.withTimezone
|
|
309
|
+
? timestampTZValue(dateToMicros(wrapper.data))
|
|
310
|
+
: timestampValue(dateToMicros(wrapper.data));
|
|
311
|
+
case 'blob':
|
|
312
|
+
return blobValue(toUint8Array(wrapper.data));
|
|
313
|
+
case 'json':
|
|
314
|
+
// JSON is stored as VARCHAR in DuckDB - stringify at binding time
|
|
315
|
+
return JSON.stringify(wrapper.data);
|
|
316
|
+
default: {
|
|
317
|
+
// Exhaustive check - TypeScript will error if a case is missing
|
|
318
|
+
const _exhaustive: never = wrapper;
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Unknown wrapper kind: ${(_exhaustive as AnyDuckDBValueWrapper).kind}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|