@leonardovida-md/drizzle-neo-duckdb 1.1.1 → 1.1.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/src/driver.ts CHANGED
@@ -23,7 +23,11 @@ import { DuckDBSession } from './session.ts';
23
23
  import { DuckDBDialect } from './dialect.ts';
24
24
  import { DuckDBSelectBuilder } from './select-builder.ts';
25
25
  import { aliasFields } from './sql/selection.ts';
26
- import type { ExecuteInBatchesOptions, RowData } from './client.ts';
26
+ import type {
27
+ ExecuteBatchesRawChunk,
28
+ ExecuteInBatchesOptions,
29
+ RowData,
30
+ } from './client.ts';
27
31
  import { closeClientConnection, isPool } from './client.ts';
28
32
  import {
29
33
  createDuckDBConnectionPool,
@@ -31,11 +35,20 @@ import {
31
35
  type DuckDBPoolConfig,
32
36
  type PoolPreset,
33
37
  } from './pool.ts';
38
+ import {
39
+ resolvePrepareCacheOption,
40
+ resolveRewriteArraysOption,
41
+ type PreparedStatementCacheConfig,
42
+ type PrepareCacheOption,
43
+ type RewriteArraysMode,
44
+ type RewriteArraysOption,
45
+ } from './options.ts';
34
46
 
35
47
  export interface PgDriverOptions {
36
48
  logger?: Logger;
37
- rewriteArrays?: boolean;
49
+ rewriteArrays?: RewriteArraysMode;
38
50
  rejectStringArrayLiterals?: boolean;
51
+ prepareCache?: PreparedStatementCacheConfig;
39
52
  }
40
53
 
41
54
  export class DuckDBDriver {
@@ -52,8 +65,9 @@ export class DuckDBDriver {
52
65
  ): DuckDBSession<Record<string, unknown>, TablesRelationalConfig> {
53
66
  return new DuckDBSession(this.client, this.dialect, schema, {
54
67
  logger: this.options.logger,
55
- rewriteArrays: this.options.rewriteArrays,
68
+ rewriteArrays: this.options.rewriteArrays ?? 'auto',
56
69
  rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
70
+ prepareCache: this.options.prepareCache,
57
71
  });
58
72
  }
59
73
  }
@@ -69,8 +83,9 @@ export interface DuckDBConnectionConfig {
69
83
  export interface DuckDBDrizzleConfig<
70
84
  TSchema extends Record<string, unknown> = Record<string, never>,
71
85
  > extends DrizzleConfig<TSchema> {
72
- rewriteArrays?: boolean;
86
+ rewriteArrays?: RewriteArraysOption;
73
87
  rejectStringArrayLiterals?: boolean;
88
+ prepareCache?: PrepareCacheOption;
74
89
  /** Pool configuration. Use preset name, size config, or false to disable. */
75
90
  pool?: DuckDBPoolConfig | PoolPreset | false;
76
91
  }
@@ -111,6 +126,8 @@ function createFromClient<
111
126
  instance?: DuckDBInstance
112
127
  ): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>> {
113
128
  const dialect = new DuckDBDialect();
129
+ const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
130
+ const prepareCache = resolvePrepareCacheOption(config.prepareCache);
114
131
 
115
132
  const logger =
116
133
  config.logger === true ? new DefaultLogger() : config.logger || undefined;
@@ -131,8 +148,9 @@ function createFromClient<
131
148
 
132
149
  const driver = new DuckDBDriver(client, dialect, {
133
150
  logger,
134
- rewriteArrays: config.rewriteArrays,
151
+ rewriteArrays: rewriteArraysMode,
135
152
  rejectStringArrayLiterals: config.rejectStringArrayLiterals,
153
+ prepareCache,
136
154
  });
137
155
  const session = driver.createSession(schema);
138
156
 
@@ -330,6 +348,13 @@ export class DuckDBDatabase<
330
348
  return this.session.executeBatches<T>(query, options);
331
349
  }
332
350
 
351
+ executeBatchesRaw(
352
+ query: SQL,
353
+ options: ExecuteInBatchesOptions = {}
354
+ ): AsyncGenerator<ExecuteBatchesRawChunk, void, void> {
355
+ return this.session.executeBatchesRaw(query, options);
356
+ }
357
+
333
358
  executeArrow(query: SQL): Promise<unknown> {
334
359
  return this.session.executeArrow(query);
335
360
  }
package/src/index.ts CHANGED
@@ -7,3 +7,4 @@ export * from './client.ts';
7
7
  export * from './pool.ts';
8
8
  export * from './olap.ts';
9
9
  export * from './value-wrappers.ts';
10
+ export * from './options.ts';
package/src/options.ts ADDED
@@ -0,0 +1,40 @@
1
+ export type RewriteArraysMode = 'auto' | 'always' | 'never';
2
+
3
+ export type RewriteArraysOption = boolean | RewriteArraysMode;
4
+
5
+ const DEFAULT_REWRITE_ARRAYS_MODE: RewriteArraysMode = 'auto';
6
+
7
+ export function resolveRewriteArraysOption(
8
+ value?: RewriteArraysOption
9
+ ): RewriteArraysMode {
10
+ if (value === undefined) return DEFAULT_REWRITE_ARRAYS_MODE;
11
+ if (value === true) return 'auto';
12
+ if (value === false) return 'never';
13
+ return value;
14
+ }
15
+
16
+ export type PrepareCacheOption = boolean | number | { size?: number };
17
+
18
+ export interface PreparedStatementCacheConfig {
19
+ size: number;
20
+ }
21
+
22
+ const DEFAULT_PREPARED_CACHE_SIZE = 32;
23
+
24
+ export function resolvePrepareCacheOption(
25
+ option?: PrepareCacheOption
26
+ ): PreparedStatementCacheConfig | undefined {
27
+ if (!option) return undefined;
28
+
29
+ if (option === true) {
30
+ return { size: DEFAULT_PREPARED_CACHE_SIZE };
31
+ }
32
+
33
+ if (typeof option === 'number') {
34
+ const size = Math.max(1, Math.floor(option));
35
+ return { size };
36
+ }
37
+
38
+ const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
39
+ return { size: Math.max(1, Math.floor(size)) };
40
+ }
package/src/session.ts CHANGED
@@ -15,7 +15,10 @@ import type {
15
15
  } from 'drizzle-orm/relations';
16
16
  import { fillPlaceholders, type Query, SQL, sql } from 'drizzle-orm/sql/sql';
17
17
  import type { Assume } from 'drizzle-orm/utils';
18
- import { adaptArrayOperators } from './sql/query-rewriters.ts';
18
+ import {
19
+ adaptArrayOperators,
20
+ qualifyJoinColumns,
21
+ } from './sql/query-rewriters.ts';
19
22
  import { mapResultRow } from './sql/result-mapper.ts';
20
23
  import { TransactionRollbackError } from 'drizzle-orm/errors';
21
24
  import type { DuckDBDialect } from './dialect.ts';
@@ -26,13 +29,20 @@ import type {
26
29
  } from './client.ts';
27
30
  import {
28
31
  executeArrowOnClient,
32
+ executeArraysOnClient,
29
33
  executeInBatches,
34
+ executeInBatchesRaw,
30
35
  executeOnClient,
31
36
  prepareParams,
37
+ type ExecuteBatchesRawChunk,
32
38
  type ExecuteInBatchesOptions,
33
39
  } from './client.ts';
34
40
  import { isPool } from './client.ts';
35
41
  import type { DuckDBConnection } from '@duckdb/node-api';
42
+ import type {
43
+ PreparedStatementCacheConfig,
44
+ RewriteArraysMode,
45
+ } from './options.ts';
36
46
 
37
47
  export type { DuckDBClientLike, RowData } from './client.ts';
38
48
 
@@ -46,6 +56,34 @@ function isSavepointSyntaxError(error: unknown): boolean {
46
56
  );
47
57
  }
48
58
 
59
+ function rewriteQuery(
60
+ mode: RewriteArraysMode,
61
+ query: string
62
+ ): { sql: string; rewritten: boolean } {
63
+ if (mode === 'never') {
64
+ return { sql: query, rewritten: false };
65
+ }
66
+
67
+ let result = query;
68
+ let wasRewritten = false;
69
+
70
+ // Rewrite Postgres array operators to DuckDB functions
71
+ const arrayRewritten = adaptArrayOperators(result);
72
+ if (arrayRewritten !== result) {
73
+ result = arrayRewritten;
74
+ wasRewritten = true;
75
+ }
76
+
77
+ // Qualify unqualified column references in JOIN ON clauses
78
+ const joinQualified = qualifyJoinColumns(result);
79
+ if (joinQualified !== result) {
80
+ result = joinQualified;
81
+ wasRewritten = true;
82
+ }
83
+
84
+ return { sql: result, rewritten: wasRewritten };
85
+ }
86
+
49
87
  export class DuckDBPreparedQuery<
50
88
  T extends PreparedQueryConfig,
51
89
  > extends PgPreparedQuery<T> {
@@ -62,8 +100,9 @@ export class DuckDBPreparedQuery<
62
100
  private customResultMapper:
63
101
  | ((rows: unknown[][]) => T['execute'])
64
102
  | undefined,
65
- private rewriteArrays: boolean,
103
+ private rewriteArraysMode: RewriteArraysMode,
66
104
  private rejectStringArrayLiterals: boolean,
105
+ private prepareCache: PreparedStatementCacheConfig | undefined,
67
106
  private warnOnStringArrayLiteral?: (sql: string) => void
68
107
  ) {
69
108
  super({ sql: queryString, params });
@@ -82,11 +121,12 @@ export class DuckDBPreparedQuery<
82
121
  : undefined,
83
122
  }
84
123
  );
85
- const rewrittenQuery = this.rewriteArrays
86
- ? adaptArrayOperators(this.queryString)
87
- : this.queryString;
124
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
125
+ this.rewriteArraysMode,
126
+ this.queryString
127
+ );
88
128
 
89
- if (this.rewriteArrays && rewrittenQuery !== this.queryString) {
129
+ if (didRewrite) {
90
130
  this.logger.logQuery(
91
131
  `[duckdb] original query before array rewrite: ${this.queryString}`,
92
132
  params
@@ -98,19 +138,30 @@ export class DuckDBPreparedQuery<
98
138
  const { fields, joinsNotNullableMap, customResultMapper } =
99
139
  this as typeof this & { joinsNotNullableMap?: Record<string, boolean> };
100
140
 
101
- const rows = await executeOnClient(this.client, rewrittenQuery, params);
141
+ if (fields) {
142
+ const { rows } = await executeArraysOnClient(
143
+ this.client,
144
+ rewrittenQuery,
145
+ params,
146
+ { prepareCache: this.prepareCache }
147
+ );
148
+
149
+ if (rows.length === 0) {
150
+ return [] as T['execute'];
151
+ }
102
152
 
103
- if (rows.length === 0 || !fields) {
104
- return rows as T['execute'];
153
+ return customResultMapper
154
+ ? customResultMapper(rows)
155
+ : rows.map((row) =>
156
+ mapResultRow<T['execute']>(fields, row, joinsNotNullableMap)
157
+ );
105
158
  }
106
159
 
107
- const rowValues = rows.map((row) => Object.values(row));
160
+ const rows = await executeOnClient(this.client, rewrittenQuery, params, {
161
+ prepareCache: this.prepareCache,
162
+ });
108
163
 
109
- return customResultMapper
110
- ? customResultMapper(rowValues)
111
- : rowValues.map((row) =>
112
- mapResultRow<T['execute']>(fields, row, joinsNotNullableMap)
113
- );
164
+ return rows as T['execute'];
114
165
  }
115
166
 
116
167
  all(
@@ -126,8 +177,9 @@ export class DuckDBPreparedQuery<
126
177
 
127
178
  export interface DuckDBSessionOptions {
128
179
  logger?: Logger;
129
- rewriteArrays?: boolean;
180
+ rewriteArrays?: RewriteArraysMode;
130
181
  rejectStringArrayLiterals?: boolean;
182
+ prepareCache?: PreparedStatementCacheConfig;
131
183
  }
132
184
 
133
185
  export class DuckDBSession<
@@ -138,8 +190,9 @@ export class DuckDBSession<
138
190
 
139
191
  protected override dialect: DuckDBDialect;
140
192
  private logger: Logger;
141
- private rewriteArrays: boolean;
193
+ private rewriteArraysMode: RewriteArraysMode;
142
194
  private rejectStringArrayLiterals: boolean;
195
+ private prepareCache: PreparedStatementCacheConfig | undefined;
143
196
  private hasWarnedArrayLiteral = false;
144
197
  private rollbackOnly = false;
145
198
 
@@ -152,8 +205,14 @@ export class DuckDBSession<
152
205
  super(dialect);
153
206
  this.dialect = dialect;
154
207
  this.logger = options.logger ?? new NoopLogger();
155
- this.rewriteArrays = options.rewriteArrays ?? true;
208
+ this.rewriteArraysMode = options.rewriteArrays ?? 'auto';
156
209
  this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
210
+ this.prepareCache = options.prepareCache;
211
+ this.options = {
212
+ ...options,
213
+ rewriteArrays: this.rewriteArraysMode,
214
+ prepareCache: this.prepareCache,
215
+ };
157
216
  }
158
217
 
159
218
  prepareQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(
@@ -173,8 +232,9 @@ export class DuckDBSession<
173
232
  fields,
174
233
  isResponseInArrayMode,
175
234
  customResultMapper,
176
- this.rewriteArrays,
235
+ this.rewriteArraysMode,
177
236
  this.rejectStringArrayLiterals,
237
+ this.prepareCache,
178
238
  this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral
179
239
  );
180
240
  }
@@ -266,11 +326,12 @@ export class DuckDBSession<
266
326
  ? undefined
267
327
  : () => this.warnOnStringArrayLiteral(builtQuery.sql),
268
328
  });
269
- const rewrittenQuery = this.rewriteArrays
270
- ? adaptArrayOperators(builtQuery.sql)
271
- : builtQuery.sql;
329
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
330
+ this.rewriteArraysMode,
331
+ builtQuery.sql
332
+ );
272
333
 
273
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
334
+ if (didRewrite) {
274
335
  this.logger.logQuery(
275
336
  `[duckdb] original query before array rewrite: ${builtQuery.sql}`,
276
337
  params
@@ -287,6 +348,36 @@ export class DuckDBSession<
287
348
  ) as AsyncGenerator<GenericRowData<T>[], void, void>;
288
349
  }
289
350
 
351
+ executeBatchesRaw(
352
+ query: SQL,
353
+ options: ExecuteInBatchesOptions = {}
354
+ ): AsyncGenerator<ExecuteBatchesRawChunk, void, void> {
355
+ this.dialect.resetPgJsonFlag();
356
+ const builtQuery = this.dialect.sqlToQuery(query);
357
+ this.dialect.assertNoPgJsonColumns();
358
+ const params = prepareParams(builtQuery.params, {
359
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
360
+ warnOnStringArrayLiteral: this.rejectStringArrayLiterals
361
+ ? undefined
362
+ : () => this.warnOnStringArrayLiteral(builtQuery.sql),
363
+ });
364
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
365
+ this.rewriteArraysMode,
366
+ builtQuery.sql
367
+ );
368
+
369
+ if (didRewrite) {
370
+ this.logger.logQuery(
371
+ `[duckdb] original query before array rewrite: ${builtQuery.sql}`,
372
+ params
373
+ );
374
+ }
375
+
376
+ this.logger.logQuery(rewrittenQuery, params);
377
+
378
+ return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
379
+ }
380
+
290
381
  async executeArrow(query: SQL): Promise<unknown> {
291
382
  this.dialect.resetPgJsonFlag();
292
383
  const builtQuery = this.dialect.sqlToQuery(query);
@@ -297,11 +388,12 @@ export class DuckDBSession<
297
388
  ? undefined
298
389
  : () => this.warnOnStringArrayLiteral(builtQuery.sql),
299
390
  });
300
- const rewrittenQuery = this.rewriteArrays
301
- ? adaptArrayOperators(builtQuery.sql)
302
- : builtQuery.sql;
391
+ const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
392
+ this.rewriteArraysMode,
393
+ builtQuery.sql
394
+ );
303
395
 
304
- if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
396
+ if (didRewrite) {
305
397
  this.logger.logQuery(
306
398
  `[duckdb] original query before array rewrite: ${builtQuery.sql}`,
307
399
  params
@@ -377,6 +469,15 @@ export class DuckDBTransaction<
377
469
  return (this as unknown as Tx).session.executeBatches<T>(query, options);
378
470
  }
379
471
 
472
+ executeBatchesRaw(
473
+ query: SQL,
474
+ options: ExecuteInBatchesOptions = {}
475
+ ): AsyncGenerator<ExecuteBatchesRawChunk, void, void> {
476
+ // Cast needed: PgTransaction doesn't expose session property in public API
477
+ type Tx = DuckDBTransactionWithInternals<TFullSchema, TSchema>;
478
+ return (this as unknown as Tx).session.executeBatchesRaw(query, options);
479
+ }
480
+
380
481
  executeArrow(query: SQL): Promise<unknown> {
381
482
  // Cast needed: PgTransaction doesn't expose session property in public API
382
483
  type Tx = DuckDBTransactionWithInternals<TFullSchema, TSchema>;