@leonardovida-md/drizzle-neo-duckdb 1.1.3 → 1.2.0
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 +2 -3
- package/dist/dialect.d.ts +3 -1
- package/dist/driver.d.ts +1 -3
- package/dist/duckdb-introspect.mjs +355 -553
- package/dist/helpers.mjs +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +374 -556
- package/dist/operators.d.ts +8 -0
- package/dist/options.d.ts +0 -3
- package/dist/session.d.ts +2 -5
- package/dist/sql/ast-transformer.d.ts +15 -0
- package/dist/sql/visitors/array-operators.d.ts +5 -0
- package/dist/sql/visitors/column-qualifier.d.ts +5 -0
- package/dist/utils.d.ts +0 -1
- package/package.json +6 -2
- package/src/columns.ts +6 -1
- package/src/dialect.ts +20 -0
- package/src/driver.ts +0 -8
- package/src/index.ts +1 -0
- package/src/operators.ts +27 -0
- package/src/options.ts +0 -15
- package/src/session.ts +10 -96
- package/src/sql/ast-transformer.ts +68 -0
- package/src/sql/visitors/array-operators.ts +214 -0
- package/src/sql/visitors/column-qualifier.ts +278 -0
- package/src/utils.ts +0 -1
- package/dist/sql/query-rewriters.d.ts +0 -14
- package/src/sql/query-rewriters.ts +0 -708
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DuckDB-native array operators. Generate DuckDB-compatible SQL directly
|
|
3
|
+
* without query rewriting.
|
|
4
|
+
*/
|
|
5
|
+
import { type SQL, type SQLWrapper } from 'drizzle-orm';
|
|
6
|
+
export declare function arrayHasAll<T>(column: SQLWrapper, values: T[] | SQLWrapper): SQL;
|
|
7
|
+
export declare function arrayHasAny<T>(column: SQLWrapper, values: T[] | SQLWrapper): SQL;
|
|
8
|
+
export declare function arrayContainedBy<T>(column: SQLWrapper, values: T[] | SQLWrapper): SQL;
|
package/dist/options.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export type RewriteArraysMode = 'auto' | 'always' | 'never';
|
|
2
|
-
export type RewriteArraysOption = boolean | RewriteArraysMode;
|
|
3
|
-
export declare function resolveRewriteArraysOption(value?: RewriteArraysOption): RewriteArraysMode;
|
|
4
1
|
export type PrepareCacheOption = boolean | number | {
|
|
5
2
|
size?: number;
|
|
6
3
|
};
|
package/dist/session.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { Assume } from 'drizzle-orm/utils';
|
|
|
10
10
|
import type { DuckDBDialect } from './dialect.ts';
|
|
11
11
|
import type { DuckDBClientLike, RowData } from './client.ts';
|
|
12
12
|
import { type ExecuteBatchesRawChunk, type ExecuteInBatchesOptions } from './client.ts';
|
|
13
|
-
import type { PreparedStatementCacheConfig
|
|
13
|
+
import type { PreparedStatementCacheConfig } from './options.ts';
|
|
14
14
|
export type { DuckDBClientLike, RowData } from './client.ts';
|
|
15
15
|
export declare class DuckDBPreparedQuery<T extends PreparedQueryConfig> extends PgPreparedQuery<T> {
|
|
16
16
|
private client;
|
|
@@ -21,19 +21,17 @@ export declare class DuckDBPreparedQuery<T extends PreparedQueryConfig> extends
|
|
|
21
21
|
private fields;
|
|
22
22
|
private _isResponseInArrayMode;
|
|
23
23
|
private customResultMapper;
|
|
24
|
-
private rewriteArraysMode;
|
|
25
24
|
private rejectStringArrayLiterals;
|
|
26
25
|
private prepareCache;
|
|
27
26
|
private warnOnStringArrayLiteral?;
|
|
28
27
|
static readonly [entityKind]: string;
|
|
29
|
-
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, queryString: string, params: unknown[], logger: Logger, fields: SelectedFieldsOrdered | undefined, _isResponseInArrayMode: boolean, customResultMapper: ((rows: unknown[][]) => T['execute']) | undefined,
|
|
28
|
+
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, queryString: string, params: unknown[], logger: Logger, fields: SelectedFieldsOrdered | undefined, _isResponseInArrayMode: boolean, customResultMapper: ((rows: unknown[][]) => T['execute']) | undefined, rejectStringArrayLiterals: boolean, prepareCache: PreparedStatementCacheConfig | undefined, warnOnStringArrayLiteral?: ((sql: string) => void) | undefined);
|
|
30
29
|
execute(placeholderValues?: Record<string, unknown> | undefined): Promise<T['execute']>;
|
|
31
30
|
all(placeholderValues?: Record<string, unknown> | undefined): Promise<T['all']>;
|
|
32
31
|
isResponseInArrayMode(): boolean;
|
|
33
32
|
}
|
|
34
33
|
export interface DuckDBSessionOptions {
|
|
35
34
|
logger?: Logger;
|
|
36
|
-
rewriteArrays?: RewriteArraysMode;
|
|
37
35
|
rejectStringArrayLiterals?: boolean;
|
|
38
36
|
prepareCache?: PreparedStatementCacheConfig;
|
|
39
37
|
}
|
|
@@ -44,7 +42,6 @@ export declare class DuckDBSession<TFullSchema extends Record<string, unknown> =
|
|
|
44
42
|
static readonly [entityKind]: string;
|
|
45
43
|
protected dialect: DuckDBDialect;
|
|
46
44
|
private logger;
|
|
47
|
-
private rewriteArraysMode;
|
|
48
45
|
private rejectStringArrayLiterals;
|
|
49
46
|
private prepareCache;
|
|
50
47
|
private hasWarnedArrayLiteral;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-based SQL transformer for DuckDB compatibility.
|
|
3
|
+
*
|
|
4
|
+
* Transforms:
|
|
5
|
+
* - Array operators: @>, <@, && -> array_has_all(), array_has_any()
|
|
6
|
+
* - JOIN column qualification: "col" = "col" -> "left"."col" = "right"."col"
|
|
7
|
+
*/
|
|
8
|
+
export type TransformResult = {
|
|
9
|
+
sql: string;
|
|
10
|
+
transformed: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function transformSQL(query: string): TransformResult;
|
|
13
|
+
export declare function needsTransformation(query: string): boolean;
|
|
14
|
+
export { transformArrayOperators } from './visitors/array-operators.ts';
|
|
15
|
+
export { qualifyJoinColumns } from './visitors/column-qualifier.ts';
|
package/dist/utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"module": "./dist/index.mjs",
|
|
4
4
|
"main": "./dist/index.mjs",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.2.0",
|
|
7
7
|
"description": "A drizzle ORM client for use with DuckDB. Based on drizzle's Postgres client.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
@@ -77,5 +77,9 @@
|
|
|
77
77
|
"src/**/*.ts",
|
|
78
78
|
"dist/*.mjs",
|
|
79
79
|
"dist/**/*.d.ts"
|
|
80
|
-
]
|
|
80
|
+
],
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@duckdb/node-bindings-darwin-arm64": "^1.4.2-r.1",
|
|
83
|
+
"node-sql-parser": "^5.3.13"
|
|
84
|
+
}
|
|
81
85
|
}
|
package/src/columns.ts
CHANGED
|
@@ -237,7 +237,12 @@ export const duckDbMap = <TData extends Record<string, any>>(
|
|
|
237
237
|
dataType() {
|
|
238
238
|
return `MAP (STRING, ${valueType})`;
|
|
239
239
|
},
|
|
240
|
-
toDriver(value: TData)
|
|
240
|
+
toDriver(value: TData) {
|
|
241
|
+
// Use SQL literals for empty maps due to DuckDB type inference issues
|
|
242
|
+
// with mapValue() when there are no entries to infer types from
|
|
243
|
+
if (Object.keys(value).length === 0) {
|
|
244
|
+
return buildMapLiteral(value, valueType);
|
|
245
|
+
}
|
|
241
246
|
return wrapMap(value, valueType);
|
|
242
247
|
},
|
|
243
248
|
fromDriver(value: TData | MapValueWrapper): TData {
|
package/src/dialect.ts
CHANGED
|
@@ -15,9 +15,13 @@ import {
|
|
|
15
15
|
} from 'drizzle-orm/pg-core';
|
|
16
16
|
import {
|
|
17
17
|
sql,
|
|
18
|
+
SQL,
|
|
18
19
|
type DriverValueEncoder,
|
|
19
20
|
type QueryTypingsValue,
|
|
20
21
|
} from 'drizzle-orm';
|
|
22
|
+
import type { QueryWithTypings } from 'drizzle-orm/sql/sql';
|
|
23
|
+
|
|
24
|
+
import { transformSQL } from './sql/ast-transformer.ts';
|
|
21
25
|
|
|
22
26
|
const enum SavepointSupport {
|
|
23
27
|
Unknown = 0,
|
|
@@ -181,4 +185,20 @@ export class DuckDBDialect extends PgDialect {
|
|
|
181
185
|
return 'none';
|
|
182
186
|
}
|
|
183
187
|
}
|
|
188
|
+
|
|
189
|
+
override sqlToQuery(
|
|
190
|
+
sqlObj: SQL,
|
|
191
|
+
invokeSource?: 'indexes' | undefined
|
|
192
|
+
): QueryWithTypings {
|
|
193
|
+
// First, let the parent generate the SQL string
|
|
194
|
+
const result = super.sqlToQuery(sqlObj, invokeSource);
|
|
195
|
+
|
|
196
|
+
// Apply AST-based transformations for DuckDB compatibility
|
|
197
|
+
const transformed = transformSQL(result.sql);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
...result,
|
|
201
|
+
sql: transformed.sql,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
184
204
|
}
|
package/src/driver.ts
CHANGED
|
@@ -37,16 +37,12 @@ import {
|
|
|
37
37
|
} from './pool.ts';
|
|
38
38
|
import {
|
|
39
39
|
resolvePrepareCacheOption,
|
|
40
|
-
resolveRewriteArraysOption,
|
|
41
40
|
type PreparedStatementCacheConfig,
|
|
42
41
|
type PrepareCacheOption,
|
|
43
|
-
type RewriteArraysMode,
|
|
44
|
-
type RewriteArraysOption,
|
|
45
42
|
} from './options.ts';
|
|
46
43
|
|
|
47
44
|
export interface PgDriverOptions {
|
|
48
45
|
logger?: Logger;
|
|
49
|
-
rewriteArrays?: RewriteArraysMode;
|
|
50
46
|
rejectStringArrayLiterals?: boolean;
|
|
51
47
|
prepareCache?: PreparedStatementCacheConfig;
|
|
52
48
|
}
|
|
@@ -65,7 +61,6 @@ export class DuckDBDriver {
|
|
|
65
61
|
): DuckDBSession<Record<string, unknown>, TablesRelationalConfig> {
|
|
66
62
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
67
63
|
logger: this.options.logger,
|
|
68
|
-
rewriteArrays: this.options.rewriteArrays ?? 'auto',
|
|
69
64
|
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
70
65
|
prepareCache: this.options.prepareCache,
|
|
71
66
|
});
|
|
@@ -83,7 +78,6 @@ export interface DuckDBConnectionConfig {
|
|
|
83
78
|
export interface DuckDBDrizzleConfig<
|
|
84
79
|
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
85
80
|
> extends DrizzleConfig<TSchema> {
|
|
86
|
-
rewriteArrays?: RewriteArraysOption;
|
|
87
81
|
rejectStringArrayLiterals?: boolean;
|
|
88
82
|
prepareCache?: PrepareCacheOption;
|
|
89
83
|
/** Pool configuration. Use preset name, size config, or false to disable. */
|
|
@@ -126,7 +120,6 @@ function createFromClient<
|
|
|
126
120
|
instance?: DuckDBInstance
|
|
127
121
|
): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>> {
|
|
128
122
|
const dialect = new DuckDBDialect();
|
|
129
|
-
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
130
123
|
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
131
124
|
|
|
132
125
|
const logger =
|
|
@@ -148,7 +141,6 @@ function createFromClient<
|
|
|
148
141
|
|
|
149
142
|
const driver = new DuckDBDriver(client, dialect, {
|
|
150
143
|
logger,
|
|
151
|
-
rewriteArrays: rewriteArraysMode,
|
|
152
144
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
153
145
|
prepareCache,
|
|
154
146
|
});
|
package/src/index.ts
CHANGED
package/src/operators.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DuckDB-native array operators. Generate DuckDB-compatible SQL directly
|
|
3
|
+
* without query rewriting.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { sql, type SQL, type SQLWrapper } from 'drizzle-orm';
|
|
7
|
+
|
|
8
|
+
export function arrayHasAll<T>(
|
|
9
|
+
column: SQLWrapper,
|
|
10
|
+
values: T[] | SQLWrapper
|
|
11
|
+
): SQL {
|
|
12
|
+
return sql`array_has_all(${column}, ${values})`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function arrayHasAny<T>(
|
|
16
|
+
column: SQLWrapper,
|
|
17
|
+
values: T[] | SQLWrapper
|
|
18
|
+
): SQL {
|
|
19
|
+
return sql`array_has_any(${column}, ${values})`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function arrayContainedBy<T>(
|
|
23
|
+
column: SQLWrapper,
|
|
24
|
+
values: T[] | SQLWrapper
|
|
25
|
+
): SQL {
|
|
26
|
+
return sql`array_has_all(${values}, ${column})`;
|
|
27
|
+
}
|
package/src/options.ts
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
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
1
|
export type PrepareCacheOption = boolean | number | { size?: number };
|
|
17
2
|
|
|
18
3
|
export interface PreparedStatementCacheConfig {
|
package/src/session.ts
CHANGED
|
@@ -15,10 +15,6 @@ 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 {
|
|
19
|
-
adaptArrayOperators,
|
|
20
|
-
qualifyJoinColumns,
|
|
21
|
-
} from './sql/query-rewriters.ts';
|
|
22
18
|
import { mapResultRow } from './sql/result-mapper.ts';
|
|
23
19
|
import { TransactionRollbackError } from 'drizzle-orm/errors';
|
|
24
20
|
import type { DuckDBDialect } from './dialect.ts';
|
|
@@ -39,10 +35,7 @@ import {
|
|
|
39
35
|
} from './client.ts';
|
|
40
36
|
import { isPool } from './client.ts';
|
|
41
37
|
import type { DuckDBConnection } from '@duckdb/node-api';
|
|
42
|
-
import type {
|
|
43
|
-
PreparedStatementCacheConfig,
|
|
44
|
-
RewriteArraysMode,
|
|
45
|
-
} from './options.ts';
|
|
38
|
+
import type { PreparedStatementCacheConfig } from './options.ts';
|
|
46
39
|
|
|
47
40
|
export type { DuckDBClientLike, RowData } from './client.ts';
|
|
48
41
|
|
|
@@ -56,34 +49,6 @@ function isSavepointSyntaxError(error: unknown): boolean {
|
|
|
56
49
|
);
|
|
57
50
|
}
|
|
58
51
|
|
|
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
|
-
|
|
87
52
|
export class DuckDBPreparedQuery<
|
|
88
53
|
T extends PreparedQueryConfig,
|
|
89
54
|
> extends PgPreparedQuery<T> {
|
|
@@ -100,7 +65,6 @@ export class DuckDBPreparedQuery<
|
|
|
100
65
|
private customResultMapper:
|
|
101
66
|
| ((rows: unknown[][]) => T['execute'])
|
|
102
67
|
| undefined,
|
|
103
|
-
private rewriteArraysMode: RewriteArraysMode,
|
|
104
68
|
private rejectStringArrayLiterals: boolean,
|
|
105
69
|
private prepareCache: PreparedStatementCacheConfig | undefined,
|
|
106
70
|
private warnOnStringArrayLiteral?: (sql: string) => void
|
|
@@ -121,19 +85,7 @@ export class DuckDBPreparedQuery<
|
|
|
121
85
|
: undefined,
|
|
122
86
|
}
|
|
123
87
|
);
|
|
124
|
-
|
|
125
|
-
this.rewriteArraysMode,
|
|
126
|
-
this.queryString
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
if (didRewrite) {
|
|
130
|
-
this.logger.logQuery(
|
|
131
|
-
`[duckdb] original query before array rewrite: ${this.queryString}`,
|
|
132
|
-
params
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
88
|
+
this.logger.logQuery(this.queryString, params);
|
|
137
89
|
|
|
138
90
|
const { fields, joinsNotNullableMap, customResultMapper } =
|
|
139
91
|
this as typeof this & { joinsNotNullableMap?: Record<string, boolean> };
|
|
@@ -141,7 +93,7 @@ export class DuckDBPreparedQuery<
|
|
|
141
93
|
if (fields) {
|
|
142
94
|
const { rows } = await executeArraysOnClient(
|
|
143
95
|
this.client,
|
|
144
|
-
|
|
96
|
+
this.queryString,
|
|
145
97
|
params,
|
|
146
98
|
{ prepareCache: this.prepareCache }
|
|
147
99
|
);
|
|
@@ -157,7 +109,7 @@ export class DuckDBPreparedQuery<
|
|
|
157
109
|
);
|
|
158
110
|
}
|
|
159
111
|
|
|
160
|
-
const rows = await executeOnClient(this.client,
|
|
112
|
+
const rows = await executeOnClient(this.client, this.queryString, params, {
|
|
161
113
|
prepareCache: this.prepareCache,
|
|
162
114
|
});
|
|
163
115
|
|
|
@@ -177,7 +129,6 @@ export class DuckDBPreparedQuery<
|
|
|
177
129
|
|
|
178
130
|
export interface DuckDBSessionOptions {
|
|
179
131
|
logger?: Logger;
|
|
180
|
-
rewriteArrays?: RewriteArraysMode;
|
|
181
132
|
rejectStringArrayLiterals?: boolean;
|
|
182
133
|
prepareCache?: PreparedStatementCacheConfig;
|
|
183
134
|
}
|
|
@@ -190,7 +141,6 @@ export class DuckDBSession<
|
|
|
190
141
|
|
|
191
142
|
protected override dialect: DuckDBDialect;
|
|
192
143
|
private logger: Logger;
|
|
193
|
-
private rewriteArraysMode: RewriteArraysMode;
|
|
194
144
|
private rejectStringArrayLiterals: boolean;
|
|
195
145
|
private prepareCache: PreparedStatementCacheConfig | undefined;
|
|
196
146
|
private hasWarnedArrayLiteral = false;
|
|
@@ -205,12 +155,10 @@ export class DuckDBSession<
|
|
|
205
155
|
super(dialect);
|
|
206
156
|
this.dialect = dialect;
|
|
207
157
|
this.logger = options.logger ?? new NoopLogger();
|
|
208
|
-
this.rewriteArraysMode = options.rewriteArrays ?? 'auto';
|
|
209
158
|
this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
|
|
210
159
|
this.prepareCache = options.prepareCache;
|
|
211
160
|
this.options = {
|
|
212
161
|
...options,
|
|
213
|
-
rewriteArrays: this.rewriteArraysMode,
|
|
214
162
|
prepareCache: this.prepareCache,
|
|
215
163
|
};
|
|
216
164
|
}
|
|
@@ -232,7 +180,6 @@ export class DuckDBSession<
|
|
|
232
180
|
fields,
|
|
233
181
|
isResponseInArrayMode,
|
|
234
182
|
customResultMapper,
|
|
235
|
-
this.rewriteArraysMode,
|
|
236
183
|
this.rejectStringArrayLiterals,
|
|
237
184
|
this.prepareCache,
|
|
238
185
|
this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral
|
|
@@ -326,23 +273,12 @@ export class DuckDBSession<
|
|
|
326
273
|
? undefined
|
|
327
274
|
: () => this.warnOnStringArrayLiteral(builtQuery.sql),
|
|
328
275
|
});
|
|
329
|
-
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
|
|
330
|
-
this.rewriteArraysMode,
|
|
331
|
-
builtQuery.sql
|
|
332
|
-
);
|
|
333
276
|
|
|
334
|
-
|
|
335
|
-
this.logger.logQuery(
|
|
336
|
-
`[duckdb] original query before array rewrite: ${builtQuery.sql}`,
|
|
337
|
-
params
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
this.logger.logQuery(rewrittenQuery, params);
|
|
277
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
342
278
|
|
|
343
279
|
return executeInBatches(
|
|
344
280
|
this.client,
|
|
345
|
-
|
|
281
|
+
builtQuery.sql,
|
|
346
282
|
params,
|
|
347
283
|
options
|
|
348
284
|
) as AsyncGenerator<GenericRowData<T>[], void, void>;
|
|
@@ -361,21 +297,10 @@ export class DuckDBSession<
|
|
|
361
297
|
? undefined
|
|
362
298
|
: () => this.warnOnStringArrayLiteral(builtQuery.sql),
|
|
363
299
|
});
|
|
364
|
-
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
|
|
365
|
-
this.rewriteArraysMode,
|
|
366
|
-
builtQuery.sql
|
|
367
|
-
);
|
|
368
300
|
|
|
369
|
-
|
|
370
|
-
this.logger.logQuery(
|
|
371
|
-
`[duckdb] original query before array rewrite: ${builtQuery.sql}`,
|
|
372
|
-
params
|
|
373
|
-
);
|
|
374
|
-
}
|
|
301
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
375
302
|
|
|
376
|
-
this.
|
|
377
|
-
|
|
378
|
-
return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
|
|
303
|
+
return executeInBatchesRaw(this.client, builtQuery.sql, params, options);
|
|
379
304
|
}
|
|
380
305
|
|
|
381
306
|
async executeArrow(query: SQL): Promise<unknown> {
|
|
@@ -388,21 +313,10 @@ export class DuckDBSession<
|
|
|
388
313
|
? undefined
|
|
389
314
|
: () => this.warnOnStringArrayLiteral(builtQuery.sql),
|
|
390
315
|
});
|
|
391
|
-
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(
|
|
392
|
-
this.rewriteArraysMode,
|
|
393
|
-
builtQuery.sql
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
if (didRewrite) {
|
|
397
|
-
this.logger.logQuery(
|
|
398
|
-
`[duckdb] original query before array rewrite: ${builtQuery.sql}`,
|
|
399
|
-
params
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
316
|
|
|
403
|
-
this.logger.logQuery(
|
|
317
|
+
this.logger.logQuery(builtQuery.sql, params);
|
|
404
318
|
|
|
405
|
-
return executeArrowOnClient(this.client,
|
|
319
|
+
return executeArrowOnClient(this.client, builtQuery.sql, params);
|
|
406
320
|
}
|
|
407
321
|
|
|
408
322
|
markRollbackOnly(): void {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-based SQL transformer for DuckDB compatibility.
|
|
3
|
+
*
|
|
4
|
+
* Transforms:
|
|
5
|
+
* - Array operators: @>, <@, && -> array_has_all(), array_has_any()
|
|
6
|
+
* - JOIN column qualification: "col" = "col" -> "left"."col" = "right"."col"
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import nodeSqlParser from 'node-sql-parser';
|
|
10
|
+
const { Parser } = nodeSqlParser;
|
|
11
|
+
import type { AST } from 'node-sql-parser';
|
|
12
|
+
|
|
13
|
+
import { transformArrayOperators } from './visitors/array-operators.ts';
|
|
14
|
+
import { qualifyJoinColumns } from './visitors/column-qualifier.ts';
|
|
15
|
+
|
|
16
|
+
const parser = new Parser();
|
|
17
|
+
|
|
18
|
+
export type TransformResult = {
|
|
19
|
+
sql: string;
|
|
20
|
+
transformed: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function transformSQL(query: string): TransformResult {
|
|
24
|
+
const needsArrayTransform =
|
|
25
|
+
query.includes('@>') || query.includes('<@') || query.includes('&&');
|
|
26
|
+
const needsJoinTransform = query.toLowerCase().includes('join');
|
|
27
|
+
|
|
28
|
+
if (!needsArrayTransform && !needsJoinTransform) {
|
|
29
|
+
return { sql: query, transformed: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const ast = parser.astify(query, { database: 'PostgreSQL' });
|
|
34
|
+
|
|
35
|
+
let transformed = false;
|
|
36
|
+
|
|
37
|
+
if (needsArrayTransform) {
|
|
38
|
+
transformed = transformArrayOperators(ast) || transformed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (needsJoinTransform) {
|
|
42
|
+
transformed = qualifyJoinColumns(ast) || transformed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!transformed) {
|
|
46
|
+
return { sql: query, transformed: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const transformedSql = parser.sqlify(ast, { database: 'PostgreSQL' });
|
|
50
|
+
|
|
51
|
+
return { sql: transformedSql, transformed: true };
|
|
52
|
+
} catch {
|
|
53
|
+
return { sql: query, transformed: false };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function needsTransformation(query: string): boolean {
|
|
58
|
+
const lower = query.toLowerCase();
|
|
59
|
+
return (
|
|
60
|
+
query.includes('@>') ||
|
|
61
|
+
query.includes('<@') ||
|
|
62
|
+
query.includes('&&') ||
|
|
63
|
+
lower.includes('join')
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { transformArrayOperators } from './visitors/array-operators.ts';
|
|
68
|
+
export { qualifyJoinColumns } from './visitors/column-qualifier.ts';
|