@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
package/src/introspect.ts
CHANGED
|
@@ -466,17 +466,17 @@ function emitConstraints(
|
|
|
466
466
|
.map((col) => `t.${toIdentifier(col)}`)
|
|
467
467
|
.join(', ')}], name: ${JSON.stringify(constraint.name)} })`
|
|
468
468
|
);
|
|
469
|
-
} else if (
|
|
470
|
-
constraint.type === 'UNIQUE' &&
|
|
471
|
-
constraint.columns.length > 1
|
|
472
|
-
) {
|
|
469
|
+
} else if (constraint.type === 'UNIQUE' && constraint.columns.length > 1) {
|
|
473
470
|
imports.pgCore.add('unique');
|
|
474
471
|
entries.push(
|
|
475
472
|
`${key}: unique(${JSON.stringify(constraint.name)}).on(${constraint.columns
|
|
476
473
|
.map((col) => `t.${toIdentifier(col)}`)
|
|
477
474
|
.join(', ')})`
|
|
478
475
|
);
|
|
479
|
-
} else if (
|
|
476
|
+
} else if (
|
|
477
|
+
constraint.type === 'FOREIGN KEY' &&
|
|
478
|
+
constraint.referencedTable
|
|
479
|
+
) {
|
|
480
480
|
imports.pgCore.add('foreignKey');
|
|
481
481
|
const targetTable = toIdentifier(constraint.referencedTable.name);
|
|
482
482
|
entries.push(
|
|
@@ -546,7 +546,10 @@ function buildDefault(defaultValue: string | null): string {
|
|
|
546
546
|
if (/^nextval\(/i.test(trimmed)) {
|
|
547
547
|
return `.default(sql\`${trimmed}\`)`;
|
|
548
548
|
}
|
|
549
|
-
if (
|
|
549
|
+
if (
|
|
550
|
+
/^current_timestamp(?:\(\))?$/i.test(trimmed) ||
|
|
551
|
+
/^now\(\)$/i.test(trimmed)
|
|
552
|
+
) {
|
|
550
553
|
return `.defaultNow()`;
|
|
551
554
|
}
|
|
552
555
|
if (trimmed === 'true' || trimmed === 'false') {
|
|
@@ -604,7 +607,11 @@ function mapDuckDbType(
|
|
|
604
607
|
|
|
605
608
|
if (upper === 'BIGINT' || upper === 'INT8' || upper === 'UBIGINT') {
|
|
606
609
|
imports.pgCore.add('bigint');
|
|
607
|
-
|
|
610
|
+
// Drizzle's bigint helper requires an explicit mode. Default to 'number'
|
|
611
|
+
// to mirror DuckDB's typical 64-bit integer behavior in JS.
|
|
612
|
+
return {
|
|
613
|
+
builder: `bigint(${columnName(column.name)}, { mode: 'number' })`,
|
|
614
|
+
};
|
|
608
615
|
}
|
|
609
616
|
|
|
610
617
|
const decimalMatch = /^DECIMAL\((\d+),(\d+)\)/i.exec(upper);
|
|
@@ -894,9 +901,7 @@ function renderImports(imports: ImportBuckets, importBasePath: string): string {
|
|
|
894
901
|
const pgCore = [...imports.pgCore];
|
|
895
902
|
if (pgCore.length) {
|
|
896
903
|
lines.push(
|
|
897
|
-
`import { ${pgCore
|
|
898
|
-
.sort()
|
|
899
|
-
.join(', ')} } from 'drizzle-orm/pg-core';`
|
|
904
|
+
`import { ${pgCore.sort().join(', ')} } from 'drizzle-orm/pg-core';`
|
|
900
905
|
);
|
|
901
906
|
}
|
|
902
907
|
|
package/src/migrator.ts
CHANGED
|
@@ -10,9 +10,7 @@ export async function migrate<TSchema extends Record<string, unknown>>(
|
|
|
10
10
|
config: DuckDbMigrationConfig
|
|
11
11
|
) {
|
|
12
12
|
const migrationConfig: MigrationConfig =
|
|
13
|
-
typeof config === 'string'
|
|
14
|
-
? { migrationsFolder: config }
|
|
15
|
-
: config;
|
|
13
|
+
typeof config === 'string' ? { migrationsFolder: config } : config;
|
|
16
14
|
|
|
17
15
|
const migrations = readMigrationFiles(migrationConfig);
|
|
18
16
|
|
package/src/olap.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { is } from 'drizzle-orm/entity';
|
|
2
|
+
import { sql, Subquery, type SQLWrapper } from 'drizzle-orm';
|
|
3
|
+
import type { AnyPgColumn, PgTable } from 'drizzle-orm/pg-core';
|
|
4
|
+
import type { PgViewBase } from 'drizzle-orm/pg-core/view-base';
|
|
5
|
+
import type { SelectedFields } from 'drizzle-orm/pg-core/query-builders';
|
|
6
|
+
import { SQL } from 'drizzle-orm/sql/sql';
|
|
7
|
+
import { Column, getTableName } from 'drizzle-orm';
|
|
8
|
+
import type { DuckDBDatabase } from './driver.ts';
|
|
9
|
+
|
|
10
|
+
export const countN = (expr: SQLWrapper = sql`*`) =>
|
|
11
|
+
sql<number>`count(${expr})`.mapWith(Number);
|
|
12
|
+
|
|
13
|
+
export const sumN = (expr: SQLWrapper) =>
|
|
14
|
+
sql<number>`sum(${expr})`.mapWith(Number);
|
|
15
|
+
|
|
16
|
+
export const avgN = (expr: SQLWrapper) =>
|
|
17
|
+
sql<number>`avg(${expr})`.mapWith(Number);
|
|
18
|
+
|
|
19
|
+
export const sumDistinctN = (expr: SQLWrapper) =>
|
|
20
|
+
sql<number>`sum(distinct ${expr})`.mapWith(Number);
|
|
21
|
+
|
|
22
|
+
export const percentileCont = (p: number, expr: SQLWrapper) =>
|
|
23
|
+
sql<number>`percentile_cont(${p}) within group (order by ${expr})`.mapWith(
|
|
24
|
+
Number
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const median = (expr: SQLWrapper) => percentileCont(0.5, expr);
|
|
28
|
+
|
|
29
|
+
export const anyValue = <T = unknown>(expr: SQLWrapper) =>
|
|
30
|
+
sql<T>`any_value(${expr})`;
|
|
31
|
+
|
|
32
|
+
type PartitionOrder =
|
|
33
|
+
| {
|
|
34
|
+
partitionBy?: SQLWrapper | SQLWrapper[];
|
|
35
|
+
orderBy?: SQLWrapper | SQLWrapper[];
|
|
36
|
+
}
|
|
37
|
+
| undefined;
|
|
38
|
+
|
|
39
|
+
function normalizeArray<T>(value?: T | T[]): T[] {
|
|
40
|
+
if (!value) return [];
|
|
41
|
+
return Array.isArray(value) ? value : [value];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function overClause(options?: PartitionOrder) {
|
|
45
|
+
const partitions = normalizeArray(options?.partitionBy);
|
|
46
|
+
const orders = normalizeArray(options?.orderBy);
|
|
47
|
+
|
|
48
|
+
const chunks: SQLWrapper[] = [];
|
|
49
|
+
|
|
50
|
+
if (partitions.length > 0) {
|
|
51
|
+
chunks.push(sql`partition by ${sql.join(partitions, sql`, `)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (orders.length > 0) {
|
|
55
|
+
chunks.push(sql`order by ${sql.join(orders, sql`, `)}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (chunks.length === 0) {
|
|
59
|
+
return sql``;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return sql`over (${sql.join(chunks, sql` `)})`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const rowNumber = (options?: PartitionOrder) =>
|
|
66
|
+
sql<number>`row_number() ${overClause(options)}`.mapWith(Number);
|
|
67
|
+
|
|
68
|
+
export const rank = (options?: PartitionOrder) =>
|
|
69
|
+
sql<number>`rank() ${overClause(options)}`.mapWith(Number);
|
|
70
|
+
|
|
71
|
+
export const denseRank = (options?: PartitionOrder) =>
|
|
72
|
+
sql<number>`dense_rank() ${overClause(options)}`.mapWith(Number);
|
|
73
|
+
|
|
74
|
+
export const lag = <T = unknown>(
|
|
75
|
+
expr: SQLWrapper,
|
|
76
|
+
offset = 1,
|
|
77
|
+
defaultValue?: SQLWrapper,
|
|
78
|
+
options?: PartitionOrder
|
|
79
|
+
) =>
|
|
80
|
+
defaultValue
|
|
81
|
+
? sql<T>`lag(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}`
|
|
82
|
+
: sql<T>`lag(${expr}, ${offset}) ${overClause(options)}`;
|
|
83
|
+
|
|
84
|
+
export const lead = <T = unknown>(
|
|
85
|
+
expr: SQLWrapper,
|
|
86
|
+
offset = 1,
|
|
87
|
+
defaultValue?: SQLWrapper,
|
|
88
|
+
options?: PartitionOrder
|
|
89
|
+
) =>
|
|
90
|
+
defaultValue
|
|
91
|
+
? sql<T>`lead(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}`
|
|
92
|
+
: sql<T>`lead(${expr}, ${offset}) ${overClause(options)}`;
|
|
93
|
+
|
|
94
|
+
type ValueExpr = SQL | SQL.Aliased | AnyPgColumn;
|
|
95
|
+
type GroupKey = ValueExpr;
|
|
96
|
+
type MeasureMap = Record<string, ValueExpr>;
|
|
97
|
+
type NonAggMap = Record<string, ValueExpr>;
|
|
98
|
+
|
|
99
|
+
function keyAlias(key: SQLWrapper, fallback: string): string {
|
|
100
|
+
if (is(key, SQL.Aliased)) {
|
|
101
|
+
return key.fieldAlias ?? fallback;
|
|
102
|
+
}
|
|
103
|
+
if (is(key, Column)) {
|
|
104
|
+
return `${getTableName(key.table)}.${key.name}`;
|
|
105
|
+
}
|
|
106
|
+
return fallback;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class OlapBuilder {
|
|
110
|
+
private source?: PgTable | Subquery | PgViewBase | SQL;
|
|
111
|
+
private keys: GroupKey[] = [];
|
|
112
|
+
private measureMap: MeasureMap = {};
|
|
113
|
+
private nonAggregates: NonAggMap = {};
|
|
114
|
+
private wrapNonAggWithAnyValue = false;
|
|
115
|
+
private orderByClauses: ValueExpr[] = [];
|
|
116
|
+
|
|
117
|
+
constructor(private db: DuckDBDatabase) {}
|
|
118
|
+
|
|
119
|
+
from(source: PgTable | Subquery | PgViewBase | SQL): this {
|
|
120
|
+
this.source = source;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
groupBy(keys: GroupKey[]): this {
|
|
125
|
+
this.keys = keys;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
measures(measures: MeasureMap): this {
|
|
130
|
+
this.measureMap = measures;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
selectNonAggregates(
|
|
135
|
+
fields: NonAggMap,
|
|
136
|
+
options: { anyValue?: boolean } = {}
|
|
137
|
+
): this {
|
|
138
|
+
this.nonAggregates = fields;
|
|
139
|
+
this.wrapNonAggWithAnyValue = options.anyValue ?? false;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
orderBy(...clauses: ValueExpr[]): this {
|
|
144
|
+
this.orderByClauses = clauses;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
build() {
|
|
149
|
+
if (!this.source) {
|
|
150
|
+
throw new Error('olap: .from() is required');
|
|
151
|
+
}
|
|
152
|
+
if (this.keys.length === 0) {
|
|
153
|
+
throw new Error('olap: .groupBy() is required');
|
|
154
|
+
}
|
|
155
|
+
if (Object.keys(this.measureMap).length === 0) {
|
|
156
|
+
throw new Error('olap: .measures() is required');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const selection: Record<string, ValueExpr> = {};
|
|
160
|
+
|
|
161
|
+
this.keys.forEach((key, idx) => {
|
|
162
|
+
const alias = keyAlias(key, `key_${idx}`);
|
|
163
|
+
selection[alias] = key;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
Object.entries(this.nonAggregates).forEach(([alias, expr]) => {
|
|
167
|
+
selection[alias] = this.wrapNonAggWithAnyValue ? anyValue(expr) : expr;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
Object.assign(selection, this.measureMap);
|
|
171
|
+
|
|
172
|
+
let query: any = this.db
|
|
173
|
+
.select(selection as SelectedFields)
|
|
174
|
+
.from(this.source!)
|
|
175
|
+
.groupBy(...this.keys);
|
|
176
|
+
|
|
177
|
+
if (this.orderByClauses.length > 0) {
|
|
178
|
+
query = query.orderBy(...this.orderByClauses);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return query;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
run() {
|
|
185
|
+
return this.build();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const olap = (db: DuckDBDatabase) => new OlapBuilder(db);
|
package/src/select-builder.ts
CHANGED
|
@@ -6,11 +6,7 @@ import {
|
|
|
6
6
|
type SelectedFields,
|
|
7
7
|
type TableLikeHasEmptySelection,
|
|
8
8
|
} from 'drizzle-orm/pg-core/query-builders';
|
|
9
|
-
import {
|
|
10
|
-
PgColumn,
|
|
11
|
-
PgTable,
|
|
12
|
-
type PgSession,
|
|
13
|
-
} from 'drizzle-orm/pg-core';
|
|
9
|
+
import { PgColumn, PgTable, type PgSession } from 'drizzle-orm/pg-core';
|
|
14
10
|
import { Subquery, ViewBaseConfig, type SQLWrapper } from 'drizzle-orm';
|
|
15
11
|
import { PgViewBase } from 'drizzle-orm/pg-core/view-base';
|
|
16
12
|
import type {
|
|
@@ -25,7 +21,7 @@ import { getTableColumns, type DrizzleTypeError } from 'drizzle-orm/utils';
|
|
|
25
21
|
interface PgViewBaseInternal<
|
|
26
22
|
TName extends string = string,
|
|
27
23
|
TExisting extends boolean = boolean,
|
|
28
|
-
TSelectedFields extends ColumnsSelection = ColumnsSelection
|
|
24
|
+
TSelectedFields extends ColumnsSelection = ColumnsSelection,
|
|
29
25
|
> extends PgViewBase<TName, TExisting, TSelectedFields> {
|
|
30
26
|
[ViewBaseConfig]?: {
|
|
31
27
|
selectedFields: SelectedFields;
|
|
@@ -34,7 +30,7 @@ interface PgViewBaseInternal<
|
|
|
34
30
|
|
|
35
31
|
export class DuckDBSelectBuilder<
|
|
36
32
|
TSelection extends SelectedFields | undefined,
|
|
37
|
-
TBuilderMode extends 'db' | 'qb' = 'db'
|
|
33
|
+
TBuilderMode extends 'db' | 'qb' = 'db',
|
|
38
34
|
> extends PgSelectBuilder<TSelection, TBuilderMode> {
|
|
39
35
|
private _fields: TSelection;
|
|
40
36
|
private _session: PgSession | undefined;
|
package/src/session.ts
CHANGED
|
@@ -20,12 +20,18 @@ import { mapResultRow } from './sql/result-mapper.ts';
|
|
|
20
20
|
import type { DuckDBDialect } from './dialect.ts';
|
|
21
21
|
import { TransactionRollbackError } from 'drizzle-orm/errors';
|
|
22
22
|
import type { DuckDBClientLike, RowData } from './client.ts';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
executeArrowOnClient,
|
|
25
|
+
executeInBatches,
|
|
26
|
+
executeOnClient,
|
|
27
|
+
prepareParams,
|
|
28
|
+
type ExecuteInBatchesOptions,
|
|
29
|
+
} from './client.ts';
|
|
24
30
|
|
|
25
31
|
export type { DuckDBClientLike, RowData } from './client.ts';
|
|
26
32
|
|
|
27
33
|
export class DuckDBPreparedQuery<
|
|
28
|
-
T extends PreparedQueryConfig
|
|
34
|
+
T extends PreparedQueryConfig,
|
|
29
35
|
> extends PgPreparedQuery<T> {
|
|
30
36
|
static readonly [entityKind]: string = 'DuckDBPreparedQuery';
|
|
31
37
|
|
|
@@ -37,7 +43,9 @@ export class DuckDBPreparedQuery<
|
|
|
37
43
|
private logger: Logger,
|
|
38
44
|
private fields: SelectedFieldsOrdered | undefined,
|
|
39
45
|
private _isResponseInArrayMode: boolean,
|
|
40
|
-
private customResultMapper:
|
|
46
|
+
private customResultMapper:
|
|
47
|
+
| ((rows: unknown[][]) => T['execute'])
|
|
48
|
+
| undefined,
|
|
41
49
|
private rewriteArrays: boolean,
|
|
42
50
|
private rejectStringArrayLiterals: boolean,
|
|
43
51
|
private warnOnStringArrayLiteral?: (sql: string) => void
|
|
@@ -71,17 +79,10 @@ export class DuckDBPreparedQuery<
|
|
|
71
79
|
|
|
72
80
|
this.logger.logQuery(rewrittenQuery, params);
|
|
73
81
|
|
|
74
|
-
const {
|
|
75
|
-
|
|
76
|
-
joinsNotNullableMap,
|
|
77
|
-
customResultMapper,
|
|
78
|
-
} = this as typeof this & { joinsNotNullableMap?: Record<string, boolean> };
|
|
82
|
+
const { fields, joinsNotNullableMap, customResultMapper } =
|
|
83
|
+
this as typeof this & { joinsNotNullableMap?: Record<string, boolean> };
|
|
79
84
|
|
|
80
|
-
const rows = await executeOnClient(
|
|
81
|
-
this.client,
|
|
82
|
-
rewrittenQuery,
|
|
83
|
-
params
|
|
84
|
-
);
|
|
85
|
+
const rows = await executeOnClient(this.client, rewrittenQuery, params);
|
|
85
86
|
|
|
86
87
|
if (rows.length === 0 || !fields) {
|
|
87
88
|
return rows as T['execute'];
|
|
@@ -115,7 +116,7 @@ export interface DuckDBSessionOptions {
|
|
|
115
116
|
|
|
116
117
|
export class DuckDBSession<
|
|
117
118
|
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
118
|
-
TSchema extends TablesRelationalConfig = Record<string, never
|
|
119
|
+
TSchema extends TablesRelationalConfig = Record<string, never>,
|
|
119
120
|
> extends PgSession<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
120
121
|
static readonly [entityKind]: string = 'DuckDBSession';
|
|
121
122
|
|
|
@@ -199,11 +200,67 @@ export class DuckDBSession<
|
|
|
199
200
|
[]
|
|
200
201
|
);
|
|
201
202
|
};
|
|
203
|
+
|
|
204
|
+
executeBatches<T extends RowData = RowData>(
|
|
205
|
+
query: SQL,
|
|
206
|
+
options: ExecuteInBatchesOptions = {}
|
|
207
|
+
): AsyncGenerator<GenericRowData<T>[], void, void> {
|
|
208
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
209
|
+
const params = prepareParams(builtQuery.params, {
|
|
210
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
211
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals
|
|
212
|
+
? undefined
|
|
213
|
+
: () => this.warnOnStringArrayLiteral(builtQuery.sql),
|
|
214
|
+
});
|
|
215
|
+
const rewrittenQuery = this.rewriteArrays
|
|
216
|
+
? adaptArrayOperators(builtQuery.sql)
|
|
217
|
+
: builtQuery.sql;
|
|
218
|
+
|
|
219
|
+
if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
|
|
220
|
+
this.logger.logQuery(
|
|
221
|
+
`[duckdb] original query before array rewrite: ${builtQuery.sql}`,
|
|
222
|
+
params
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
227
|
+
|
|
228
|
+
return executeInBatches(
|
|
229
|
+
this.client,
|
|
230
|
+
rewrittenQuery,
|
|
231
|
+
params,
|
|
232
|
+
options
|
|
233
|
+
) as AsyncGenerator<GenericRowData<T>[], void, void>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async executeArrow(query: SQL): Promise<unknown> {
|
|
237
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
238
|
+
const params = prepareParams(builtQuery.params, {
|
|
239
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
240
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals
|
|
241
|
+
? undefined
|
|
242
|
+
: () => this.warnOnStringArrayLiteral(builtQuery.sql),
|
|
243
|
+
});
|
|
244
|
+
const rewrittenQuery = this.rewriteArrays
|
|
245
|
+
? adaptArrayOperators(builtQuery.sql)
|
|
246
|
+
: builtQuery.sql;
|
|
247
|
+
|
|
248
|
+
if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
|
|
249
|
+
this.logger.logQuery(
|
|
250
|
+
`[duckdb] original query before array rewrite: ${builtQuery.sql}`,
|
|
251
|
+
params
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
256
|
+
|
|
257
|
+
return executeArrowOnClient(this.client, rewrittenQuery, params);
|
|
258
|
+
}
|
|
202
259
|
}
|
|
203
260
|
|
|
204
261
|
type PgTransactionInternals<
|
|
205
262
|
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
206
|
-
TSchema extends TablesRelationalConfig = Record<string, never
|
|
263
|
+
TSchema extends TablesRelationalConfig = Record<string, never>,
|
|
207
264
|
> = {
|
|
208
265
|
dialect: DuckDBDialect;
|
|
209
266
|
session: DuckDBSession<TFullSchema, TSchema>;
|
|
@@ -211,13 +268,13 @@ type PgTransactionInternals<
|
|
|
211
268
|
|
|
212
269
|
type DuckDBTransactionWithInternals<
|
|
213
270
|
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
214
|
-
TSchema extends TablesRelationalConfig = Record<string, never
|
|
271
|
+
TSchema extends TablesRelationalConfig = Record<string, never>,
|
|
215
272
|
> = PgTransactionInternals<TFullSchema, TSchema> &
|
|
216
273
|
DuckDBTransaction<TFullSchema, TSchema>;
|
|
217
274
|
|
|
218
275
|
export class DuckDBTransaction<
|
|
219
276
|
TFullSchema extends Record<string, unknown>,
|
|
220
|
-
TSchema extends TablesRelationalConfig
|
|
277
|
+
TSchema extends TablesRelationalConfig,
|
|
221
278
|
> extends PgTransaction<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
222
279
|
static readonly [entityKind]: string = 'DuckDBTransaction';
|
|
223
280
|
|
|
@@ -246,6 +303,19 @@ export class DuckDBTransaction<
|
|
|
246
303
|
);
|
|
247
304
|
}
|
|
248
305
|
|
|
306
|
+
executeBatches<T extends RowData = RowData>(
|
|
307
|
+
query: SQL,
|
|
308
|
+
options: ExecuteInBatchesOptions = {}
|
|
309
|
+
): AsyncGenerator<GenericRowData<T>[], void, void> {
|
|
310
|
+
type Tx = DuckDBTransactionWithInternals<TFullSchema, TSchema>;
|
|
311
|
+
return (this as unknown as Tx).session.executeBatches<T>(query, options);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
executeArrow(query: SQL): Promise<unknown> {
|
|
315
|
+
type Tx = DuckDBTransactionWithInternals<TFullSchema, TSchema>;
|
|
316
|
+
return (this as unknown as Tx).session.executeArrow(query);
|
|
317
|
+
}
|
|
318
|
+
|
|
249
319
|
override async transaction<T>(
|
|
250
320
|
transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>
|
|
251
321
|
): Promise<T> {
|
|
@@ -268,7 +338,6 @@ export type GenericTableData<T = RowData> = T[];
|
|
|
268
338
|
const arrayLiteralWarning =
|
|
269
339
|
'Received a stringified Postgres-style array literal. Use duckDbList()/duckDbArray() or pass native arrays instead. You can also set rejectStringArrayLiterals=true to throw.';
|
|
270
340
|
|
|
271
|
-
|
|
272
341
|
export interface DuckDBQueryResultHKT extends PgQueryResultHKT {
|
|
273
342
|
type: GenericTableData<Assume<this['row'], RowData>>;
|
|
274
343
|
}
|
|
@@ -85,10 +85,7 @@ export function adaptArrayOperators(query: string): string {
|
|
|
85
85
|
let idx = rewritten.indexOf(token);
|
|
86
86
|
while (idx !== -1) {
|
|
87
87
|
const [leftStart, leftExpr] = walkLeft(rewritten, idx - 1);
|
|
88
|
-
const [rightEnd, rightExpr] = walkRight(
|
|
89
|
-
rewritten,
|
|
90
|
-
idx + token.length
|
|
91
|
-
);
|
|
88
|
+
const [rightEnd, rightExpr] = walkRight(rewritten, idx + token.length);
|
|
92
89
|
|
|
93
90
|
const left = leftExpr.trim();
|
|
94
91
|
const right = rightExpr.trim();
|
|
@@ -98,9 +95,7 @@ export function adaptArrayOperators(query: string): string {
|
|
|
98
95
|
})`;
|
|
99
96
|
|
|
100
97
|
rewritten =
|
|
101
|
-
rewritten.slice(0, leftStart) +
|
|
102
|
-
replacement +
|
|
103
|
-
rewritten.slice(rightEnd);
|
|
98
|
+
rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
|
|
104
99
|
|
|
105
100
|
idx = rewritten.indexOf(token, leftStart + replacement.length);
|
|
106
101
|
}
|
|
@@ -136,7 +131,9 @@ export function queryAdapter(query: string): string {
|
|
|
136
131
|
if (noTableProp) {
|
|
137
132
|
const [, column, alias] = noTableProp;
|
|
138
133
|
const asAlias = ` as '${column}'`;
|
|
139
|
-
return alias
|
|
134
|
+
return alias
|
|
135
|
+
? trimmedField.replace(alias, asAlias)
|
|
136
|
+
: `${trimmedField}${asAlias}`;
|
|
140
137
|
}
|
|
141
138
|
|
|
142
139
|
return trimmedField;
|
package/src/sql/result-mapper.ts
CHANGED
|
@@ -88,7 +88,10 @@ function normalizeTimestampString(
|
|
|
88
88
|
return value;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function normalizeTimestamp(
|
|
91
|
+
function normalizeTimestamp(
|
|
92
|
+
value: unknown,
|
|
93
|
+
withTimezone: boolean
|
|
94
|
+
): Date | unknown {
|
|
92
95
|
if (value instanceof Date) {
|
|
93
96
|
return value;
|
|
94
97
|
}
|
|
@@ -96,8 +99,7 @@ function normalizeTimestamp(value: unknown, withTimezone: boolean): Date | unkno
|
|
|
96
99
|
const hasOffset =
|
|
97
100
|
value.endsWith('Z') || /[+-]\d{2}:?\d{2}$/.test(value.trim());
|
|
98
101
|
const spaced = value.replace(' ', 'T');
|
|
99
|
-
const normalized =
|
|
100
|
-
withTimezone || hasOffset ? spaced : `${spaced}+00`;
|
|
102
|
+
const normalized = withTimezone || hasOffset ? spaced : `${spaced}+00`;
|
|
101
103
|
return new Date(normalized);
|
|
102
104
|
}
|
|
103
105
|
return value;
|
|
@@ -177,9 +179,7 @@ function mapDriverValue(
|
|
|
177
179
|
if (normalized instanceof Date) {
|
|
178
180
|
return normalized;
|
|
179
181
|
}
|
|
180
|
-
return decoder.mapFromDriverValue(
|
|
181
|
-
toDecoderInput(decoder, normalized)
|
|
182
|
-
);
|
|
182
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalized));
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
if (is(decoder, PgDateString)) {
|
package/src/sql/selection.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Column,
|
|
3
|
-
SQL,
|
|
4
|
-
getTableName,
|
|
5
|
-
is,
|
|
6
|
-
sql,
|
|
7
|
-
} from 'drizzle-orm';
|
|
1
|
+
import { Column, SQL, getTableName, is, sql } from 'drizzle-orm';
|
|
8
2
|
import type { SelectedFields } from 'drizzle-orm/pg-core';
|
|
9
3
|
|
|
10
4
|
function mapEntries(
|
|
@@ -38,8 +32,7 @@ function mapEntries(
|
|
|
38
32
|
}
|
|
39
33
|
|
|
40
34
|
if (is(value, SQL) || is(value, Column)) {
|
|
41
|
-
const aliased =
|
|
42
|
-
is(value, SQL) ? value : sql`${value}`.mapWith(value);
|
|
35
|
+
const aliased = is(value, SQL) ? value : sql`${value}`.mapWith(value);
|
|
43
36
|
return [key, aliased.as(qualified)];
|
|
44
37
|
}
|
|
45
38
|
|