@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.
@@ -0,0 +1,104 @@
1
+ import { type DuckDBValue } from '@duckdb/node-api';
2
+ /**
3
+ * Symbol used to identify wrapped DuckDB values for native binding.
4
+ * Uses Symbol.for() to ensure cross-module compatibility.
5
+ */
6
+ export declare const DUCKDB_VALUE_MARKER: unique symbol;
7
+ /**
8
+ * Type identifier for each wrapper kind.
9
+ */
10
+ export type DuckDBValueKind = 'list' | 'array' | 'struct' | 'map' | 'timestamp' | 'blob' | 'json';
11
+ /**
12
+ * Base interface for all tagged DuckDB value wrappers.
13
+ */
14
+ export interface DuckDBValueWrapper<TKind extends DuckDBValueKind = DuckDBValueKind, TData = unknown> {
15
+ readonly [DUCKDB_VALUE_MARKER]: true;
16
+ readonly kind: TKind;
17
+ readonly data: TData;
18
+ }
19
+ /**
20
+ * List wrapper - maps to DuckDBListValue
21
+ */
22
+ export interface ListValueWrapper extends DuckDBValueWrapper<'list', unknown[]> {
23
+ readonly elementType?: string;
24
+ }
25
+ /**
26
+ * Array wrapper (fixed size) - maps to DuckDBArrayValue
27
+ */
28
+ export interface ArrayValueWrapper extends DuckDBValueWrapper<'array', unknown[]> {
29
+ readonly elementType?: string;
30
+ readonly fixedLength?: number;
31
+ }
32
+ /**
33
+ * Struct wrapper - maps to DuckDBStructValue
34
+ */
35
+ export interface StructValueWrapper extends DuckDBValueWrapper<'struct', Record<string, unknown>> {
36
+ readonly schema?: Record<string, string>;
37
+ }
38
+ /**
39
+ * Map wrapper - maps to DuckDBMapValue
40
+ */
41
+ export interface MapValueWrapper extends DuckDBValueWrapper<'map', Record<string, unknown>> {
42
+ readonly valueType?: string;
43
+ }
44
+ /**
45
+ * Timestamp wrapper - maps to DuckDBTimestampValue or DuckDBTimestampTZValue
46
+ */
47
+ export interface TimestampValueWrapper extends DuckDBValueWrapper<'timestamp', Date | string> {
48
+ readonly withTimezone: boolean;
49
+ readonly precision?: number;
50
+ }
51
+ /**
52
+ * Blob wrapper - maps to DuckDBBlobValue
53
+ */
54
+ export interface BlobValueWrapper extends DuckDBValueWrapper<'blob', Buffer | Uint8Array> {
55
+ }
56
+ /**
57
+ * JSON wrapper - delays JSON.stringify() to binding time.
58
+ * DuckDB stores JSON as VARCHAR internally.
59
+ */
60
+ export interface JsonValueWrapper extends DuckDBValueWrapper<'json', unknown> {
61
+ }
62
+ /**
63
+ * Union of all wrapper types for exhaustive type checking.
64
+ */
65
+ export type AnyDuckDBValueWrapper = ListValueWrapper | ArrayValueWrapper | StructValueWrapper | MapValueWrapper | TimestampValueWrapper | BlobValueWrapper | JsonValueWrapper;
66
+ /**
67
+ * Type guard to check if a value is a tagged DuckDB wrapper.
68
+ * Optimized for fast detection in the hot path.
69
+ */
70
+ export declare function isDuckDBWrapper(value: unknown): value is AnyDuckDBValueWrapper;
71
+ /**
72
+ * Create a list wrapper for variable-length lists.
73
+ */
74
+ export declare function wrapList(data: unknown[], elementType?: string): ListValueWrapper;
75
+ /**
76
+ * Create an array wrapper for fixed-length arrays.
77
+ */
78
+ export declare function wrapArray(data: unknown[], elementType?: string, fixedLength?: number): ArrayValueWrapper;
79
+ /**
80
+ * Create a struct wrapper for named field structures.
81
+ */
82
+ export declare function wrapStruct(data: Record<string, unknown>, schema?: Record<string, string>): StructValueWrapper;
83
+ /**
84
+ * Create a map wrapper for key-value maps.
85
+ */
86
+ export declare function wrapMap(data: Record<string, unknown>, valueType?: string): MapValueWrapper;
87
+ /**
88
+ * Create a timestamp wrapper.
89
+ */
90
+ export declare function wrapTimestamp(data: Date | string, withTimezone: boolean, precision?: number): TimestampValueWrapper;
91
+ /**
92
+ * Create a blob wrapper for binary data.
93
+ */
94
+ export declare function wrapBlob(data: Buffer | Uint8Array): BlobValueWrapper;
95
+ /**
96
+ * Create a JSON wrapper that delays JSON.stringify() to binding time.
97
+ * This ensures consistent handling with other wrapped types.
98
+ */
99
+ export declare function wrapJson(data: unknown): JsonValueWrapper;
100
+ /**
101
+ * Convert a wrapper to a DuckDB Node API value.
102
+ * Uses exhaustive switch for compile-time safety.
103
+ */
104
+ export declare function wrapperToNodeApiValue(wrapper: AnyDuckDBValueWrapper, toValue: (v: unknown) => DuckDBValue): DuckDBValue;
package/package.json CHANGED
@@ -3,14 +3,17 @@
3
3
  "module": "./dist/index.mjs",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.ts",
6
- "version": "1.0.2",
6
+ "version": "1.0.3",
7
7
  "description": "A drizzle ORM client for use with DuckDB. Based on drizzle's Postgres client.",
8
8
  "type": "module",
9
9
  "scripts": {
10
10
  "build": "bun build --target=node ./src/index.ts --outfile=./dist/index.mjs --packages=external && bun build --target=node ./src/bin/duckdb-introspect.ts --outfile=./dist/duckdb-introspect.mjs --packages=external && bun run build:declarations",
11
11
  "build:declarations": "tsc --emitDeclarationOnly --project tsconfig.types.json",
12
12
  "test": "vitest",
13
- "t": "vitest --watch --ui"
13
+ "t": "vitest --watch --ui",
14
+ "bench": "vitest bench --runInBand test/perf",
15
+ "perf:run": "bun run scripts/run-perf.ts",
16
+ "perf:compare": "bun run scripts/compare-perf.ts"
14
17
  },
15
18
  "bin": {
16
19
  "duckdb-introspect": "dist/duckdb-introspect.mjs"
@@ -33,7 +36,8 @@
33
36
  "prettier": "^3.5.3",
34
37
  "typescript": "^5.8.2",
35
38
  "uuid": "^10.0.0",
36
- "vitest": "^1.6.0"
39
+ "vitest": "^1.6.0",
40
+ "tinybench": "^2.7.1"
37
41
  },
38
42
  "repository": {
39
43
  "type": "git",
@@ -40,11 +40,17 @@ function parseArgs(argv: string[]): CliOptions {
40
40
  break;
41
41
  case '--schema':
42
42
  case '--schemas':
43
- options.schemas = argv[++i]?.split(',').map((s) => s.trim()).filter(Boolean);
43
+ options.schemas = argv[++i]
44
+ ?.split(',')
45
+ .map((s) => s.trim())
46
+ .filter(Boolean);
44
47
  break;
45
48
  case '--out':
46
49
  case '--outFile':
47
- options.outFile = path.resolve(process.cwd(), argv[++i] ?? 'drizzle/schema.ts');
50
+ options.outFile = path.resolve(
51
+ process.cwd(),
52
+ argv[++i] ?? 'drizzle/schema.ts'
53
+ );
48
54
  break;
49
55
  case '--include-views':
50
56
  case '--includeViews':
@@ -133,7 +139,10 @@ async function main() {
133
139
 
134
140
  console.log(`Wrote schema to ${options.outFile}`);
135
141
  } finally {
136
- if ('closeSync' in connection && typeof connection.closeSync === 'function') {
142
+ if (
143
+ 'closeSync' in connection &&
144
+ typeof connection.closeSync === 'function'
145
+ ) {
137
146
  connection.closeSync();
138
147
  }
139
148
  }
package/src/client.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import {
2
2
  listValue,
3
+ timestampValue,
3
4
  type DuckDBConnection,
4
5
  type DuckDBValue,
5
6
  } from '@duckdb/node-api';
7
+ import {
8
+ DUCKDB_VALUE_MARKER,
9
+ wrapperToNodeApiValue,
10
+ type AnyDuckDBValueWrapper,
11
+ } from './value-wrappers.ts';
6
12
 
7
13
  export type DuckDBClientLike = DuckDBConnection;
8
14
  export type RowData = Record<string, unknown>;
@@ -50,13 +56,62 @@ export function prepareParams(
50
56
  });
51
57
  }
52
58
 
59
+ /**
60
+ * Convert a value to DuckDB Node API value.
61
+ * Handles wrapper types and plain values for backward compatibility.
62
+ * Optimized for the common case (primitives) in the hot path.
63
+ */
53
64
  function toNodeApiValue(value: unknown): DuckDBValue {
65
+ // Fast path 1: null/undefined
66
+ if (value == null) return null;
67
+
68
+ // Fast path 2: primitives (most common)
69
+ const t = typeof value;
70
+ if (t === 'string' || t === 'number' || t === 'bigint' || t === 'boolean') {
71
+ return value as DuckDBValue;
72
+ }
73
+
74
+ // Fast path 3: pre-wrapped DuckDB value (Symbol check ~2-3ns)
75
+ if (t === 'object' && DUCKDB_VALUE_MARKER in (value as object)) {
76
+ return wrapperToNodeApiValue(
77
+ value as AnyDuckDBValueWrapper,
78
+ toNodeApiValue
79
+ );
80
+ }
81
+
82
+ // Legacy path: plain arrays (backward compatibility)
54
83
  if (Array.isArray(value)) {
55
84
  return listValue(value.map((inner) => toNodeApiValue(inner)));
56
85
  }
86
+
87
+ // Date conversion to timestamp
88
+ if (value instanceof Date) {
89
+ return timestampValue(BigInt(value.getTime()) * 1000n);
90
+ }
91
+
92
+ // Fallback for unknown objects
57
93
  return value as DuckDBValue;
58
94
  }
59
95
 
96
+ function deduplicateColumns(columns: string[]): string[] {
97
+ const seen: Record<string, number> = {};
98
+ return columns.map((col) => {
99
+ const count = seen[col] ?? 0;
100
+ seen[col] = count + 1;
101
+ return count === 0 ? col : `${col}_${count}`;
102
+ });
103
+ }
104
+
105
+ function mapRowsToObjects(columns: string[], rows: unknown[][]): RowData[] {
106
+ return rows.map((vals) => {
107
+ const obj: Record<string, unknown> = {};
108
+ columns.forEach((col, idx) => {
109
+ obj[col] = vals[idx];
110
+ });
111
+ return obj;
112
+ }) as RowData[];
113
+ }
114
+
60
115
  export async function closeClientConnection(
61
116
  connection: DuckDBConnection
62
117
  ): Promise<void> {
@@ -65,10 +120,7 @@ export async function closeClientConnection(
65
120
  return;
66
121
  }
67
122
 
68
- if (
69
- 'closeSync' in connection &&
70
- typeof connection.closeSync === 'function'
71
- ) {
123
+ if ('closeSync' in connection && typeof connection.closeSync === 'function') {
72
124
  connection.closeSync();
73
125
  return;
74
126
  }
@@ -92,19 +144,84 @@ export async function executeOnClient(
92
144
  : undefined;
93
145
  const result = await client.run(query, values);
94
146
  const rows = await result.getRowsJS();
95
- const columns = result.columnNames();
96
- const seen: Record<string, number> = {};
97
- const uniqueColumns = columns.map((col) => {
98
- const count = seen[col] ?? 0;
99
- seen[col] = count + 1;
100
- return count === 0 ? col : `${col}_${count}`;
101
- });
147
+ const columns =
148
+ // prefer deduplicated names when available (Node API >=1.4.2)
149
+ result.deduplicatedColumnNames?.() ?? result.columnNames();
150
+ const uniqueColumns = deduplicateColumns(columns);
102
151
 
103
- return (rows ?? []).map((vals) => {
104
- const obj: Record<string, unknown> = {};
105
- uniqueColumns.forEach((col, idx) => {
106
- obj[col] = vals[idx];
107
- });
108
- return obj;
109
- }) as RowData[];
152
+ return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
153
+ }
154
+
155
+ export interface ExecuteInBatchesOptions {
156
+ rowsPerChunk?: number;
157
+ }
158
+
159
+ /**
160
+ * Stream results from DuckDB in batches to avoid fully materializing rows in JS.
161
+ */
162
+ export async function* executeInBatches(
163
+ client: DuckDBClientLike,
164
+ query: string,
165
+ params: unknown[],
166
+ options: ExecuteInBatchesOptions = {}
167
+ ): AsyncGenerator<RowData[], void, void> {
168
+ const rowsPerChunk =
169
+ options.rowsPerChunk && options.rowsPerChunk > 0
170
+ ? options.rowsPerChunk
171
+ : 100_000;
172
+ const values =
173
+ params.length > 0
174
+ ? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
175
+ : undefined;
176
+
177
+ const result = await client.stream(query, values);
178
+ const columns =
179
+ // prefer deduplicated names when available (Node API >=1.4.2)
180
+ result.deduplicatedColumnNames?.() ?? result.columnNames();
181
+ const uniqueColumns = deduplicateColumns(columns);
182
+
183
+ let buffer: RowData[] = [];
184
+
185
+ for await (const chunk of result.yieldRowsJs()) {
186
+ const objects = mapRowsToObjects(uniqueColumns, chunk);
187
+ for (const row of objects) {
188
+ buffer.push(row);
189
+ if (buffer.length >= rowsPerChunk) {
190
+ yield buffer;
191
+ buffer = [];
192
+ }
193
+ }
194
+ }
195
+
196
+ if (buffer.length > 0) {
197
+ yield buffer;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Return columnar results when the underlying node-api exposes an Arrow/columnar API.
203
+ * Falls back to column-major JS arrays when Arrow is unavailable.
204
+ */
205
+ export async function executeArrowOnClient(
206
+ client: DuckDBClientLike,
207
+ query: string,
208
+ params: unknown[]
209
+ ): Promise<unknown> {
210
+ const values =
211
+ params.length > 0
212
+ ? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
213
+ : undefined;
214
+ const result = await client.run(query, values);
215
+
216
+ const maybeArrow =
217
+ (result as unknown as { toArrow?: () => Promise<unknown> }).toArrow ??
218
+ (result as unknown as { getArrowTable?: () => Promise<unknown> })
219
+ .getArrowTable;
220
+
221
+ if (typeof maybeArrow === 'function') {
222
+ return await maybeArrow.call(result);
223
+ }
224
+
225
+ // Fallback: return column-major JS arrays to avoid per-row object creation.
226
+ return result.getColumnsObjectJS();
110
227
  }
package/src/columns.ts CHANGED
@@ -1,6 +1,18 @@
1
1
  import { sql, type SQL } from 'drizzle-orm';
2
2
  import type { SQLWrapper } from 'drizzle-orm/sql/sql';
3
3
  import { customType } from 'drizzle-orm/pg-core';
4
+ import {
5
+ wrapList,
6
+ wrapArray,
7
+ wrapMap,
8
+ wrapBlob,
9
+ wrapJson,
10
+ type ListValueWrapper,
11
+ type ArrayValueWrapper,
12
+ type MapValueWrapper,
13
+ type BlobValueWrapper,
14
+ type JsonValueWrapper,
15
+ } from './value-wrappers.ts';
4
16
 
5
17
  type IntColType =
6
18
  | 'SMALLINT'
@@ -93,7 +105,9 @@ function formatLiteral(value: unknown, typeHint?: string): string {
93
105
  }
94
106
 
95
107
  const str =
96
- typeof value === 'string' ? value : JSON.stringify(value) ?? String(value);
108
+ typeof value === 'string'
109
+ ? value
110
+ : (JSON.stringify(value) ?? String(value));
97
111
 
98
112
  const escaped = str.replace(/'/g, "''");
99
113
  // Simple quoting based on hint.
@@ -140,7 +154,10 @@ function buildStructLiteral(
140
154
  return sql`struct_pack(${sql.join(parts, sql.raw(', '))})`;
141
155
  }
142
156
 
143
- function buildMapLiteral(value: Record<string, unknown>, valueType?: string): SQL {
157
+ function buildMapLiteral(
158
+ value: Record<string, unknown>,
159
+ valueType?: string
160
+ ): SQL {
144
161
  const keys = Object.keys(value);
145
162
  const vals = Object.values(value);
146
163
  const keyList = buildListLiteral(keys, 'TEXT');
@@ -155,14 +172,17 @@ export const duckDbList = <TData = unknown>(
155
172
  name: string,
156
173
  elementType: AnyColType
157
174
  ) =>
158
- customType<{ data: TData[]; driverData: SQL | unknown[] | string }>({
175
+ customType<{
176
+ data: TData[];
177
+ driverData: ListValueWrapper | unknown[] | string;
178
+ }>({
159
179
  dataType() {
160
180
  return `${elementType}[]`;
161
181
  },
162
- toDriver(value: TData[]) {
163
- return buildListLiteral(value, elementType);
182
+ toDriver(value: TData[]): ListValueWrapper {
183
+ return wrapList(value, elementType);
164
184
  },
165
- fromDriver(value: unknown[] | string | SQL): TData[] {
185
+ fromDriver(value: unknown[] | string | ListValueWrapper): TData[] {
166
186
  if (Array.isArray(value)) {
167
187
  return value as TData[];
168
188
  }
@@ -181,16 +201,19 @@ export const duckDbArray = <TData = unknown>(
181
201
  elementType: AnyColType,
182
202
  fixedLength?: number
183
203
  ) =>
184
- customType<{ data: TData[]; driverData: SQL | unknown[] | string }>({
204
+ customType<{
205
+ data: TData[];
206
+ driverData: ArrayValueWrapper | unknown[] | string;
207
+ }>({
185
208
  dataType() {
186
209
  return fixedLength
187
210
  ? `${elementType}[${fixedLength}]`
188
211
  : `${elementType}[]`;
189
212
  },
190
- toDriver(value: TData[]) {
191
- return buildListLiteral(value, elementType);
213
+ toDriver(value: TData[]): ArrayValueWrapper {
214
+ return wrapArray(value, elementType, fixedLength);
192
215
  },
193
- fromDriver(value: unknown[] | string | SQL): TData[] {
216
+ fromDriver(value: unknown[] | string | ArrayValueWrapper): TData[] {
194
217
  if (Array.isArray(value)) {
195
218
  return value as TData[];
196
219
  }
@@ -208,15 +231,15 @@ export const duckDbMap = <TData extends Record<string, any>>(
208
231
  name: string,
209
232
  valueType: AnyColType | ListColType | ArrayColType
210
233
  ) =>
211
- customType<{ data: TData; driverData: TData }>({
212
- dataType() {
234
+ customType<{ data: TData; driverData: MapValueWrapper | TData }>({
235
+ dataType() {
213
236
  return `MAP (STRING, ${valueType})`;
214
237
  },
215
- toDriver(value: TData) {
216
- return buildMapLiteral(value, valueType);
238
+ toDriver(value: TData): MapValueWrapper {
239
+ return wrapMap(value, valueType);
217
240
  },
218
- fromDriver(value: TData): TData {
219
- return value;
241
+ fromDriver(value: TData | MapValueWrapper): TData {
242
+ return value as TData;
220
243
  },
221
244
  })(name);
222
245
 
@@ -233,6 +256,8 @@ export const duckDbStruct = <TData extends Record<string, any>>(
233
256
  return `STRUCT (${fields.join(', ')})`;
234
257
  },
235
258
  toDriver(value: TData) {
259
+ // Use SQL literals for structs due to DuckDB type inference issues
260
+ // with nested empty lists
236
261
  return buildStructLiteral(value, schema);
237
262
  },
238
263
  fromDriver(value: TData | string): TData {
@@ -247,15 +272,24 @@ export const duckDbStruct = <TData extends Record<string, any>>(
247
272
  },
248
273
  })(name);
249
274
 
275
+ /**
276
+ * JSON column type that wraps values and delays JSON.stringify() to binding time.
277
+ * This ensures consistent handling with other wrapped types.
278
+ *
279
+ * Note: DuckDB stores JSON as VARCHAR internally, so the final binding
280
+ * is always a stringified JSON value.
281
+ */
250
282
  export const duckDbJson = <TData = unknown>(name: string) =>
251
- customType<{ data: TData; driverData: SQL | string }>({
283
+ customType<{ data: TData; driverData: JsonValueWrapper | SQL | string }>({
252
284
  dataType() {
253
285
  return 'JSON';
254
286
  },
255
- toDriver(value: TData) {
287
+ toDriver(value: TData): JsonValueWrapper | SQL | string {
288
+ // Pass through strings directly
256
289
  if (typeof value === 'string') {
257
290
  return value;
258
291
  }
292
+ // Pass through SQL objects (for raw SQL expressions)
259
293
  if (
260
294
  value !== null &&
261
295
  typeof value === 'object' &&
@@ -263,9 +297,10 @@ export const duckDbJson = <TData = unknown>(name: string) =>
263
297
  ) {
264
298
  return value as unknown as SQL;
265
299
  }
266
- return JSON.stringify(value ?? null);
300
+ // Wrap non-string values for delayed stringify at binding time
301
+ return wrapJson(value);
267
302
  },
268
- fromDriver(value: SQL | string) {
303
+ fromDriver(value: SQL | string | JsonValueWrapper) {
269
304
  if (typeof value !== 'string') {
270
305
  return value as unknown as TData;
271
306
  }
@@ -283,14 +318,14 @@ export const duckDbJson = <TData = unknown>(name: string) =>
283
318
 
284
319
  export const duckDbBlob = customType<{
285
320
  data: Buffer;
321
+ driverData: BlobValueWrapper;
286
322
  default: false;
287
323
  }>({
288
324
  dataType() {
289
325
  return 'BLOB';
290
326
  },
291
- toDriver(value: Buffer) {
292
- const hexString = value.toString('hex');
293
- return sql`from_hex(${hexString})`;
327
+ toDriver(value: Buffer): BlobValueWrapper {
328
+ return wrapBlob(value);
294
329
  },
295
330
  });
296
331
 
@@ -322,10 +357,7 @@ interface TimestampOptions {
322
357
  precision?: number;
323
358
  }
324
359
 
325
- export const duckDbTimestamp = (
326
- name: string,
327
- options: TimestampOptions = {}
328
- ) =>
360
+ export const duckDbTimestamp = (name: string, options: TimestampOptions = {}) =>
329
361
  customType<{
330
362
  data: Date | string;
331
363
  driverData: SQL | string | Date;
@@ -338,6 +370,7 @@ export const duckDbTimestamp = (
338
370
  return `TIMESTAMP${precision}`;
339
371
  },
340
372
  toDriver(value: Date | string) {
373
+ // Use SQL literals for timestamps due to Bun/DuckDB bigint binding issues
341
374
  const iso = value instanceof Date ? value.toISOString() : value;
342
375
  const normalized = iso.replace('T', ' ').replace('Z', '+00');
343
376
  const typeKeyword = options.withTimezone ? 'TIMESTAMPTZ' : 'TIMESTAMP';
@@ -353,11 +386,9 @@ export const duckDbTimestamp = (
353
386
  if (value instanceof Date) {
354
387
  return value;
355
388
  }
356
- const stringValue =
357
- typeof value === 'string' ? value : value.toString();
389
+ const stringValue = typeof value === 'string' ? value : value.toString();
358
390
  const hasOffset =
359
- stringValue.endsWith('Z') ||
360
- /[+-]\d{2}:?\d{2}$/.test(stringValue);
391
+ stringValue.endsWith('Z') || /[+-]\d{2}:?\d{2}$/.test(stringValue);
361
392
  const normalized = hasOffset
362
393
  ? stringValue.replace(' ', 'T')
363
394
  : `${stringValue.replace(' ', 'T')}Z`;
@@ -374,11 +405,9 @@ export const duckDbDate = (name: string) =>
374
405
  return value;
375
406
  },
376
407
  fromDriver(value: string | Date) {
377
- const str =
378
- value instanceof Date
379
- ? value.toISOString().slice(0, 10)
380
- : value;
381
- return str;
408
+ const str =
409
+ value instanceof Date ? value.toISOString().slice(0, 10) : value;
410
+ return str;
382
411
  },
383
412
  })(name);
384
413
 
package/src/dialect.ts CHANGED
@@ -52,8 +52,8 @@ export class DuckDBDialect extends PgDialect {
52
52
 
53
53
  const migrationTableCreate = sql`
54
54
  CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsSchema)}.${sql.identifier(
55
- migrationsTable
56
- )} (
55
+ migrationsTable
56
+ )} (
57
57
  id integer PRIMARY KEY default nextval('${sql.raw(sequenceLiteral)}'),
58
58
  hash text NOT NULL,
59
59
  created_at bigint
package/src/driver.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  type TablesRelationalConfig,
13
13
  } from 'drizzle-orm/relations';
14
14
  import { type DrizzleConfig } from 'drizzle-orm/utils';
15
+ import type { SQL } from 'drizzle-orm/sql/sql';
15
16
  import type {
16
17
  DuckDBClientLike,
17
18
  DuckDBQueryResultHKT,
@@ -21,6 +22,7 @@ import { DuckDBSession } from './session.ts';
21
22
  import { DuckDBDialect } from './dialect.ts';
22
23
  import { DuckDBSelectBuilder } from './select-builder.ts';
23
24
  import { aliasFields } from './sql/selection.ts';
25
+ import type { ExecuteInBatchesOptions, RowData } from './client.ts';
24
26
 
25
27
  export interface PgDriverOptions {
26
28
  logger?: Logger;
@@ -49,14 +51,14 @@ export class DuckDBDriver {
49
51
  }
50
52
 
51
53
  export interface DuckDBDrizzleConfig<
52
- TSchema extends Record<string, unknown> = Record<string, never>
54
+ TSchema extends Record<string, unknown> = Record<string, never>,
53
55
  > extends DrizzleConfig<TSchema> {
54
56
  rewriteArrays?: boolean;
55
57
  rejectStringArrayLiterals?: boolean;
56
58
  }
57
59
 
58
60
  export function drizzle<
59
- TSchema extends Record<string, unknown> = Record<string, never>
61
+ TSchema extends Record<string, unknown> = Record<string, never>,
60
62
  >(
61
63
  client: DuckDBClientLike,
62
64
  config: DuckDBDrizzleConfig<TSchema> = {}
@@ -95,7 +97,8 @@ export function drizzle<
95
97
 
96
98
  export class DuckDBDatabase<
97
99
  TFullSchema extends Record<string, unknown> = Record<string, never>,
98
- TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>
100
+ TSchema extends
101
+ TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
99
102
  > extends PgDatabase<DuckDBQueryResultHKT, TFullSchema, TSchema> {
100
103
  static readonly [entityKind]: string = 'DuckDBDatabase';
101
104
 
@@ -111,9 +114,9 @@ export class DuckDBDatabase<
111
114
  select<TSelection extends SelectedFields>(
112
115
  fields: TSelection
113
116
  ): DuckDBSelectBuilder<TSelection>;
114
- select(fields?: SelectedFields): DuckDBSelectBuilder<
115
- SelectedFields | undefined
116
- > {
117
+ select(
118
+ fields?: SelectedFields
119
+ ): DuckDBSelectBuilder<SelectedFields | undefined> {
117
120
  const selectedFields = fields ? aliasFields(fields) : undefined;
118
121
 
119
122
  return new DuckDBSelectBuilder({
@@ -123,6 +126,17 @@ export class DuckDBDatabase<
123
126
  });
124
127
  }
125
128
 
129
+ executeBatches<T extends RowData = RowData>(
130
+ query: SQL,
131
+ options: ExecuteInBatchesOptions = {}
132
+ ): AsyncGenerator<T[], void, void> {
133
+ return this.session.executeBatches<T>(query, options);
134
+ }
135
+
136
+ executeArrow(query: SQL): Promise<unknown> {
137
+ return this.session.executeArrow(query);
138
+ }
139
+
126
140
  override async transaction<T>(
127
141
  transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>
128
142
  ): Promise<T> {
package/src/index.ts CHANGED
@@ -3,3 +3,6 @@ export * from './session.ts';
3
3
  export * from './columns.ts';
4
4
  export * from './migrator.ts';
5
5
  export * from './introspect.ts';
6
+ export * from './client.ts';
7
+ export * from './olap.ts';
8
+ export * from './value-wrappers.ts';