@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/README.md +44 -85
- package/dist/client.d.ts +15 -1
- package/dist/columns.d.ts +3 -2
- package/dist/driver.d.ts +7 -3
- package/dist/duckdb-introspect.mjs +595 -50
- package/dist/helpers.mjs +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +621 -50
- package/dist/options.d.ts +10 -0
- package/dist/session.d.ts +11 -5
- package/dist/sql/query-rewriters.d.ts +12 -0
- package/package.json +1 -1
- package/src/client.ts +300 -40
- package/src/columns.ts +51 -4
- package/src/driver.ts +30 -5
- package/src/index.ts +1 -0
- package/src/options.ts +40 -0
- package/src/session.ts +128 -27
- package/src/sql/query-rewriters.ts +503 -0
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 {
|
|
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?:
|
|
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?:
|
|
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:
|
|
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
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 {
|
|
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
|
|
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 =
|
|
86
|
-
|
|
87
|
-
|
|
124
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
|
|
125
|
+
this.rewriteArraysMode,
|
|
126
|
+
this.queryString
|
|
127
|
+
);
|
|
88
128
|
|
|
89
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
153
|
+
return customResultMapper
|
|
154
|
+
? customResultMapper(rows)
|
|
155
|
+
: rows.map((row) =>
|
|
156
|
+
mapResultRow<T['execute']>(fields, row, joinsNotNullableMap)
|
|
157
|
+
);
|
|
105
158
|
}
|
|
106
159
|
|
|
107
|
-
const
|
|
160
|
+
const rows = await executeOnClient(this.client, rewrittenQuery, params, {
|
|
161
|
+
prepareCache: this.prepareCache,
|
|
162
|
+
});
|
|
108
163
|
|
|
109
|
-
return
|
|
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?:
|
|
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
|
|
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.
|
|
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.
|
|
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 =
|
|
270
|
-
|
|
271
|
-
|
|
329
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
|
|
330
|
+
this.rewriteArraysMode,
|
|
331
|
+
builtQuery.sql
|
|
332
|
+
);
|
|
272
333
|
|
|
273
|
-
if (
|
|
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 =
|
|
301
|
-
|
|
302
|
-
|
|
391
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
|
|
392
|
+
this.rewriteArraysMode,
|
|
393
|
+
builtQuery.sql
|
|
394
|
+
);
|
|
303
395
|
|
|
304
|
-
if (
|
|
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>;
|