@duckdbfan/drizzle-duckdb 0.0.7 → 1.3.2
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 +349 -62
- package/dist/bin/duckdb-introspect.d.ts +2 -0
- package/dist/client.d.ts +42 -0
- package/dist/columns.d.ts +100 -9
- package/dist/dialect.d.ts +27 -2
- package/dist/driver.d.ts +53 -37
- package/dist/duckdb-introspect.mjs +2890 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.mjs +360 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +3015 -228
- package/dist/introspect.d.ts +74 -0
- package/dist/migrator.d.ts +3 -2
- package/dist/olap.d.ts +46 -0
- package/dist/operators.d.ts +8 -0
- package/dist/options.d.ts +7 -0
- package/dist/pool.d.ts +30 -0
- package/dist/select-builder.d.ts +31 -0
- package/dist/session.d.ts +33 -8
- package/dist/sql/ast-transformer.d.ts +33 -0
- package/dist/sql/result-mapper.d.ts +9 -0
- package/dist/sql/selection.d.ts +2 -0
- package/dist/sql/visitors/array-operators.d.ts +5 -0
- package/dist/sql/visitors/column-qualifier.d.ts +10 -0
- package/dist/sql/visitors/generate-series-alias.d.ts +13 -0
- package/dist/sql/visitors/union-with-hoister.d.ts +11 -0
- package/dist/utils.d.ts +2 -5
- package/dist/value-wrappers-core.d.ts +42 -0
- package/dist/value-wrappers.d.ts +8 -0
- package/package.json +53 -16
- package/src/bin/duckdb-introspect.ts +181 -0
- package/src/client.ts +528 -0
- package/src/columns.ts +420 -65
- package/src/dialect.ts +111 -15
- package/src/driver.ts +266 -180
- package/src/helpers.ts +18 -0
- package/src/index.ts +8 -1
- package/src/introspect.ts +935 -0
- package/src/migrator.ts +10 -5
- package/src/olap.ts +190 -0
- package/src/operators.ts +27 -0
- package/src/options.ts +25 -0
- package/src/pool.ts +274 -0
- package/src/select-builder.ts +110 -0
- package/src/session.ts +306 -66
- package/src/sql/ast-transformer.ts +170 -0
- package/src/sql/result-mapper.ts +303 -0
- package/src/sql/selection.ts +60 -0
- package/src/sql/visitors/array-operators.ts +214 -0
- package/src/sql/visitors/column-qualifier.ts +586 -0
- package/src/sql/visitors/generate-series-alias.ts +291 -0
- package/src/sql/visitors/union-with-hoister.ts +106 -0
- package/src/utils.ts +2 -222
- package/src/value-wrappers-core.ts +168 -0
- package/src/value-wrappers.ts +165 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST visitor to rewrite Postgres style generate_series aliases.
|
|
3
|
+
*
|
|
4
|
+
* Postgres lets you reference a generate_series alias as a column:
|
|
5
|
+
* FROM generate_series(...) AS gs
|
|
6
|
+
* SELECT gs::date
|
|
7
|
+
*
|
|
8
|
+
* DuckDB treats gs as a table alias, and the column is generate_series.
|
|
9
|
+
* This visitor rewrites unqualified column refs that match a
|
|
10
|
+
* generate_series alias to gs.generate_series.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
AST,
|
|
15
|
+
Binary,
|
|
16
|
+
ColumnRefItem,
|
|
17
|
+
ExpressionValue,
|
|
18
|
+
From,
|
|
19
|
+
Join,
|
|
20
|
+
Select,
|
|
21
|
+
OrderBy,
|
|
22
|
+
Column,
|
|
23
|
+
} from 'node-sql-parser';
|
|
24
|
+
|
|
25
|
+
function getColumnName(col: ColumnRefItem): string | null {
|
|
26
|
+
if (typeof col.column === 'string') {
|
|
27
|
+
return col.column;
|
|
28
|
+
}
|
|
29
|
+
if (col.column && 'expr' in col.column && col.column.expr?.value) {
|
|
30
|
+
return String(col.column.expr.value);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isColumnRef(expr: ExpressionValue): expr is ColumnRefItem {
|
|
36
|
+
return (
|
|
37
|
+
typeof expr === 'object' &&
|
|
38
|
+
expr !== null &&
|
|
39
|
+
'type' in expr &&
|
|
40
|
+
expr.type === 'column_ref'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isBinaryExpr(
|
|
45
|
+
expr: ExpressionValue | Binary | null | undefined
|
|
46
|
+
): expr is Binary {
|
|
47
|
+
return (
|
|
48
|
+
!!expr &&
|
|
49
|
+
typeof expr === 'object' &&
|
|
50
|
+
'type' in expr &&
|
|
51
|
+
(expr as { type?: string }).type === 'binary_expr'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getGenerateSeriesAliases(from: Select['from']): Set<string> {
|
|
56
|
+
const aliases = new Set<string>();
|
|
57
|
+
if (!from || !Array.isArray(from)) return aliases;
|
|
58
|
+
|
|
59
|
+
for (const f of from) {
|
|
60
|
+
if ('expr' in f && f.expr && typeof f.expr === 'object') {
|
|
61
|
+
const exprObj = f.expr as Record<string, unknown>;
|
|
62
|
+
if (exprObj.type === 'function' && 'name' in exprObj) {
|
|
63
|
+
const nameObj = exprObj.name as Record<string, unknown> | undefined;
|
|
64
|
+
const nameParts = nameObj?.name as
|
|
65
|
+
| Array<Record<string, unknown>>
|
|
66
|
+
| undefined;
|
|
67
|
+
const fnName = nameParts?.[0]?.value;
|
|
68
|
+
if (
|
|
69
|
+
typeof fnName === 'string' &&
|
|
70
|
+
fnName.toLowerCase() === 'generate_series'
|
|
71
|
+
) {
|
|
72
|
+
const alias = typeof f.as === 'string' ? f.as : null;
|
|
73
|
+
if (alias && !alias.includes('(')) {
|
|
74
|
+
aliases.add(alias);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return aliases;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function rewriteAliasColumnRef(col: ColumnRefItem, alias: string): void {
|
|
85
|
+
col.table = alias;
|
|
86
|
+
col.column = { expr: { type: 'default', value: 'generate_series' } };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function walkExpression(
|
|
90
|
+
expr: ExpressionValue | null | undefined,
|
|
91
|
+
aliases: Set<string>
|
|
92
|
+
): boolean {
|
|
93
|
+
if (!expr || typeof expr !== 'object') return false;
|
|
94
|
+
|
|
95
|
+
let transformed = false;
|
|
96
|
+
const exprObj = expr as Record<string, unknown>;
|
|
97
|
+
|
|
98
|
+
if (isColumnRef(expr)) {
|
|
99
|
+
if (!('table' in expr) || !expr.table) {
|
|
100
|
+
const colName = getColumnName(expr);
|
|
101
|
+
if (colName && aliases.has(colName)) {
|
|
102
|
+
rewriteAliasColumnRef(expr, colName);
|
|
103
|
+
transformed = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return transformed;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isBinaryExpr(expr)) {
|
|
110
|
+
const binary = expr as Binary;
|
|
111
|
+
transformed =
|
|
112
|
+
walkExpression(binary.left as ExpressionValue, aliases) || transformed;
|
|
113
|
+
transformed =
|
|
114
|
+
walkExpression(binary.right as ExpressionValue, aliases) || transformed;
|
|
115
|
+
return transformed;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (exprObj.type === 'unary_expr' && exprObj.expr) {
|
|
119
|
+
transformed =
|
|
120
|
+
walkExpression(exprObj.expr as ExpressionValue, aliases) || transformed;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (exprObj.type === 'cast' && exprObj.expr) {
|
|
124
|
+
transformed =
|
|
125
|
+
walkExpression(exprObj.expr as ExpressionValue, aliases) || transformed;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (exprObj.type === 'case') {
|
|
129
|
+
if (exprObj.expr) {
|
|
130
|
+
transformed =
|
|
131
|
+
walkExpression(exprObj.expr as ExpressionValue, aliases) || transformed;
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(exprObj.args)) {
|
|
134
|
+
for (const whenClause of exprObj.args as Array<Record<string, unknown>>) {
|
|
135
|
+
if (whenClause.cond) {
|
|
136
|
+
transformed =
|
|
137
|
+
walkExpression(whenClause.cond as ExpressionValue, aliases) ||
|
|
138
|
+
transformed;
|
|
139
|
+
}
|
|
140
|
+
if (whenClause.result) {
|
|
141
|
+
transformed =
|
|
142
|
+
walkExpression(whenClause.result as ExpressionValue, aliases) ||
|
|
143
|
+
transformed;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if ('args' in exprObj && exprObj.args) {
|
|
150
|
+
const args = exprObj.args as Record<string, unknown>;
|
|
151
|
+
if (Array.isArray(args.value)) {
|
|
152
|
+
for (const arg of args.value as ExpressionValue[]) {
|
|
153
|
+
transformed = walkExpression(arg, aliases) || transformed;
|
|
154
|
+
}
|
|
155
|
+
} else if (args.expr) {
|
|
156
|
+
transformed =
|
|
157
|
+
walkExpression(args.expr as ExpressionValue, aliases) || transformed;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if ('over' in exprObj && exprObj.over && typeof exprObj.over === 'object') {
|
|
162
|
+
const over = exprObj.over as Record<string, unknown>;
|
|
163
|
+
if (Array.isArray(over.partition)) {
|
|
164
|
+
for (const part of over.partition as ExpressionValue[]) {
|
|
165
|
+
transformed = walkExpression(part, aliases) || transformed;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(over.orderby)) {
|
|
169
|
+
for (const order of over.orderby as ExpressionValue[]) {
|
|
170
|
+
transformed = walkExpression(order, aliases) || transformed;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if ('ast' in exprObj && exprObj.ast) {
|
|
176
|
+
const subAst = exprObj.ast as Select;
|
|
177
|
+
if (subAst.type === 'select') {
|
|
178
|
+
transformed = walkSelect(subAst) || transformed;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (exprObj.type === 'expr_list' && Array.isArray(exprObj.value)) {
|
|
183
|
+
for (const item of exprObj.value as ExpressionValue[]) {
|
|
184
|
+
transformed = walkExpression(item, aliases) || transformed;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return transformed;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function walkFrom(from: Select['from'], aliases: Set<string>): boolean {
|
|
192
|
+
if (!from || !Array.isArray(from)) return false;
|
|
193
|
+
|
|
194
|
+
let transformed = false;
|
|
195
|
+
|
|
196
|
+
for (const f of from) {
|
|
197
|
+
if ('join' in f) {
|
|
198
|
+
const join = f as Join;
|
|
199
|
+
transformed =
|
|
200
|
+
walkExpression(join.on as ExpressionValue, aliases) || transformed;
|
|
201
|
+
}
|
|
202
|
+
if ('expr' in f && f.expr && 'ast' in f.expr) {
|
|
203
|
+
transformed = walkSelect(f.expr.ast as Select) || transformed;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return transformed;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function walkSelect(select: Select): boolean {
|
|
211
|
+
let transformed = false;
|
|
212
|
+
const aliases = getGenerateSeriesAliases(select.from);
|
|
213
|
+
|
|
214
|
+
if (select.with) {
|
|
215
|
+
for (const cte of select.with) {
|
|
216
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
217
|
+
if (cteSelect && cteSelect.type === 'select') {
|
|
218
|
+
transformed = walkSelect(cteSelect as Select) || transformed;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
transformed = walkFrom(select.from, aliases) || transformed;
|
|
224
|
+
|
|
225
|
+
transformed = walkExpression(select.where, aliases) || transformed;
|
|
226
|
+
|
|
227
|
+
if (select.having) {
|
|
228
|
+
if (Array.isArray(select.having)) {
|
|
229
|
+
for (const h of select.having) {
|
|
230
|
+
transformed =
|
|
231
|
+
walkExpression(h as ExpressionValue, aliases) || transformed;
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
transformed =
|
|
235
|
+
walkExpression(select.having as ExpressionValue, aliases) ||
|
|
236
|
+
transformed;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (Array.isArray(select.columns)) {
|
|
241
|
+
for (const col of select.columns as Column[]) {
|
|
242
|
+
if ('expr' in col) {
|
|
243
|
+
transformed =
|
|
244
|
+
walkExpression(col.expr as ExpressionValue, aliases) || transformed;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (Array.isArray(select.groupby)) {
|
|
250
|
+
for (const g of select.groupby as ExpressionValue[]) {
|
|
251
|
+
transformed = walkExpression(g, aliases) || transformed;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (Array.isArray(select.orderby)) {
|
|
256
|
+
for (const order of select.orderby as OrderBy[]) {
|
|
257
|
+
if (order.expr) {
|
|
258
|
+
transformed =
|
|
259
|
+
walkExpression(order.expr as ExpressionValue, aliases) || transformed;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (select._orderby) {
|
|
265
|
+
for (const order of select._orderby as OrderBy[]) {
|
|
266
|
+
if (order.expr) {
|
|
267
|
+
transformed =
|
|
268
|
+
walkExpression(order.expr as ExpressionValue, aliases) || transformed;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (select._next) {
|
|
274
|
+
transformed = walkSelect(select._next) || transformed;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return transformed;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function rewriteGenerateSeriesAliases(ast: AST | AST[]): boolean {
|
|
281
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
282
|
+
let transformed = false;
|
|
283
|
+
|
|
284
|
+
for (const stmt of statements) {
|
|
285
|
+
if (stmt.type === 'select') {
|
|
286
|
+
transformed = walkSelect(stmt as Select) || transformed;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return transformed;
|
|
291
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST visitor to hoist WITH clauses out of UNION and other set operations.
|
|
3
|
+
*
|
|
4
|
+
* Drizzle can emit SQL like:
|
|
5
|
+
* (with a as (...) select ...) union (with b as (...) select ...)
|
|
6
|
+
*
|
|
7
|
+
* DuckDB 1.4.x has an internal binder bug for this pattern.
|
|
8
|
+
* We merge per arm CTEs into a single top level WITH when names do not collide.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AST, Select, From } from 'node-sql-parser';
|
|
12
|
+
|
|
13
|
+
function getCteName(cte: { name?: unknown }): string | null {
|
|
14
|
+
const nameObj = cte.name as Record<string, unknown> | undefined;
|
|
15
|
+
if (!nameObj) return null;
|
|
16
|
+
const value = nameObj.value;
|
|
17
|
+
if (typeof value === 'string') return value;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hoistWithInSelect(select: Select): boolean {
|
|
22
|
+
if (!select.set_op || !select._next) return false;
|
|
23
|
+
|
|
24
|
+
const arms: Select[] = [];
|
|
25
|
+
let current: Select | null = select;
|
|
26
|
+
while (current && current.type === 'select') {
|
|
27
|
+
arms.push(current);
|
|
28
|
+
current = current._next as Select | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const mergedWith: NonNullable<Select['with']> = [];
|
|
32
|
+
const seen = new Set<string>();
|
|
33
|
+
let hasWithBeyondFirst = false;
|
|
34
|
+
|
|
35
|
+
for (const arm of arms) {
|
|
36
|
+
if (arm.with && arm.with.length > 0) {
|
|
37
|
+
if (arm !== arms[0]) {
|
|
38
|
+
hasWithBeyondFirst = true;
|
|
39
|
+
}
|
|
40
|
+
for (const cte of arm.with) {
|
|
41
|
+
const cteName = getCteName(cte);
|
|
42
|
+
if (!cteName) return false;
|
|
43
|
+
if (seen.has(cteName)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
seen.add(cteName);
|
|
47
|
+
mergedWith.push(cte);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!hasWithBeyondFirst) return false;
|
|
53
|
+
|
|
54
|
+
arms[0].with = mergedWith;
|
|
55
|
+
if ('parentheses_symbol' in arms[0]) {
|
|
56
|
+
(arms[0] as Select & { parentheses_symbol?: boolean }).parentheses_symbol =
|
|
57
|
+
false;
|
|
58
|
+
}
|
|
59
|
+
for (let i = 1; i < arms.length; i++) {
|
|
60
|
+
arms[i].with = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function walkSelect(select: Select): boolean {
|
|
67
|
+
let transformed = false;
|
|
68
|
+
|
|
69
|
+
if (select.with) {
|
|
70
|
+
for (const cte of select.with) {
|
|
71
|
+
const cteSelect = cte.stmt?.ast ?? cte.stmt;
|
|
72
|
+
if (cteSelect && cteSelect.type === 'select') {
|
|
73
|
+
transformed = walkSelect(cteSelect as Select) || transformed;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(select.from)) {
|
|
79
|
+
for (const from of select.from as From[]) {
|
|
80
|
+
if ('expr' in from && from.expr && 'ast' in from.expr) {
|
|
81
|
+
transformed = walkSelect(from.expr.ast as Select) || transformed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
transformed = hoistWithInSelect(select) || transformed;
|
|
87
|
+
|
|
88
|
+
if (select._next) {
|
|
89
|
+
transformed = walkSelect(select._next) || transformed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return transformed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function hoistUnionWith(ast: AST | AST[]): boolean {
|
|
96
|
+
const statements = Array.isArray(ast) ? ast : [ast];
|
|
97
|
+
let transformed = false;
|
|
98
|
+
|
|
99
|
+
for (const stmt of statements) {
|
|
100
|
+
if (stmt.type === 'select') {
|
|
101
|
+
transformed = walkSelect(stmt as Select) || transformed;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return transformed;
|
|
106
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,222 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
type SelectedFieldsOrdered,
|
|
4
|
-
type AnyColumn,
|
|
5
|
-
type DriverValueDecoder,
|
|
6
|
-
is,
|
|
7
|
-
Column,
|
|
8
|
-
SQL,
|
|
9
|
-
getTableName,
|
|
10
|
-
sql,
|
|
11
|
-
} from 'drizzle-orm';
|
|
12
|
-
import { PgCustomColumn, type SelectedFields } from 'drizzle-orm/pg-core';
|
|
13
|
-
|
|
14
|
-
// Need to get around "decoder" property being marked as internal
|
|
15
|
-
type SQLInternal<T = unknown> = SQL<T> & {
|
|
16
|
-
decoder: DriverValueDecoder<T, any>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export function mapResultRow<TResult>(
|
|
20
|
-
columns: SelectedFieldsOrdered<AnyColumn>,
|
|
21
|
-
row: unknown[],
|
|
22
|
-
joinsNotNullableMap: Record<string, boolean> | undefined
|
|
23
|
-
): TResult {
|
|
24
|
-
// Key -> nested object key, value -> table name if all fields in the nested object are from the same table, false otherwise
|
|
25
|
-
const nullifyMap: Record<string, string | false> = {};
|
|
26
|
-
|
|
27
|
-
const result = columns.reduce<Record<string, any>>(
|
|
28
|
-
(result, { path, field }, columnIndex) => {
|
|
29
|
-
let decoder: DriverValueDecoder<unknown, unknown>;
|
|
30
|
-
if (is(field, Column)) {
|
|
31
|
-
decoder = field;
|
|
32
|
-
} else if (is(field, SQL)) {
|
|
33
|
-
decoder = (field as SQLInternal).decoder;
|
|
34
|
-
} else {
|
|
35
|
-
const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
|
|
36
|
-
|
|
37
|
-
if (is(col, PgCustomColumn)) {
|
|
38
|
-
decoder = col;
|
|
39
|
-
} else {
|
|
40
|
-
decoder = (field.sql as SQLInternal).decoder;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
let node = result;
|
|
44
|
-
for (const [pathChunkIndex, pathChunk] of path.entries()) {
|
|
45
|
-
if (pathChunkIndex < path.length - 1) {
|
|
46
|
-
if (!(pathChunk in node)) {
|
|
47
|
-
node[pathChunk] = {};
|
|
48
|
-
}
|
|
49
|
-
node = node[pathChunk];
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const rawValue = row[columnIndex]!;
|
|
54
|
-
|
|
55
|
-
const value = (node[pathChunk] =
|
|
56
|
-
rawValue === null ? null : decoder.mapFromDriverValue(rawValue));
|
|
57
|
-
|
|
58
|
-
if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
|
|
59
|
-
const objectName = path[0]!;
|
|
60
|
-
if (!(objectName in nullifyMap)) {
|
|
61
|
-
nullifyMap[objectName] =
|
|
62
|
-
value === null ? getTableName(field.table) : false;
|
|
63
|
-
} else if (
|
|
64
|
-
typeof nullifyMap[objectName] === 'string' &&
|
|
65
|
-
nullifyMap[objectName] !== getTableName(field.table)
|
|
66
|
-
) {
|
|
67
|
-
nullifyMap[objectName] = false;
|
|
68
|
-
}
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// may need to add a condition for non-Aliased SQL
|
|
73
|
-
if (
|
|
74
|
-
joinsNotNullableMap &&
|
|
75
|
-
is(field, SQL.Aliased) &&
|
|
76
|
-
path.length === 2
|
|
77
|
-
) {
|
|
78
|
-
const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
|
|
79
|
-
const tableName = col?.table && getTableName(col?.table);
|
|
80
|
-
|
|
81
|
-
if (!tableName) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const objectName = path[0]!;
|
|
86
|
-
|
|
87
|
-
if (!(objectName in nullifyMap)) {
|
|
88
|
-
nullifyMap[objectName] = value === null ? tableName : false;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (nullifyMap[objectName] && nullifyMap[objectName] !== tableName) {
|
|
93
|
-
nullifyMap[objectName] = false;
|
|
94
|
-
}
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
},
|
|
100
|
-
{}
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
// Nullify all nested objects from nullifyMap that are nullable
|
|
104
|
-
if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
|
|
105
|
-
for (const [objectName, tableName] of Object.entries(nullifyMap)) {
|
|
106
|
-
if (typeof tableName === 'string' && !joinsNotNullableMap[tableName]) {
|
|
107
|
-
result[objectName] = null;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return result as TResult;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function aliasFields(
|
|
116
|
-
fields: SelectedFields,
|
|
117
|
-
fullJoin = false
|
|
118
|
-
): SelectedFields {
|
|
119
|
-
return Object.fromEntries(
|
|
120
|
-
Object.entries(fields)
|
|
121
|
-
.filter(([key]) => key !== 'enableRLS')
|
|
122
|
-
.map(([key, value]) => {
|
|
123
|
-
if (fullJoin && is(value, Column)) {
|
|
124
|
-
return [
|
|
125
|
-
key,
|
|
126
|
-
sql`${value}`.as(`${getTableName(value.table)}.${value.name}`),
|
|
127
|
-
];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (fullJoin && is(value, SQL)) {
|
|
131
|
-
const col = value
|
|
132
|
-
.getSQL()
|
|
133
|
-
.queryChunks.find((chunk) => is(chunk, Column));
|
|
134
|
-
|
|
135
|
-
const tableName = col?.table && getTableName(col?.table);
|
|
136
|
-
|
|
137
|
-
return [key, value.as(tableName ? `${tableName}.${key}` : key)];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (is(value, SQL) || is(value, Column)) {
|
|
141
|
-
return [key, (is(value, SQL) ? value : sql`${value}`).as(key)];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (is(value, SQL.Aliased)) {
|
|
145
|
-
return [key, value];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// todo: should probably make this recursive?
|
|
149
|
-
if (typeof value === 'object') {
|
|
150
|
-
const parentKey = key;
|
|
151
|
-
|
|
152
|
-
return [
|
|
153
|
-
key,
|
|
154
|
-
Object.fromEntries(
|
|
155
|
-
Object.entries(value)
|
|
156
|
-
.filter(([childKey]) => childKey !== 'enableRLS')
|
|
157
|
-
.map(([childKey, childValue]) => [
|
|
158
|
-
childKey,
|
|
159
|
-
(is(childValue, SQL) ? childValue : sql`${childValue}`).as(
|
|
160
|
-
`${parentKey}.${childKey}`
|
|
161
|
-
),
|
|
162
|
-
])
|
|
163
|
-
),
|
|
164
|
-
];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return [key, value];
|
|
168
|
-
})
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// DuckDB names returned variables differently than Postgres
|
|
173
|
-
// so we need to remap them to match the Postgres names
|
|
174
|
-
|
|
175
|
-
const selectionRegex = /select\s+(.+)\s+from/i;
|
|
176
|
-
// const tableIdPropSelectionRegex = /("(.+)"\."(.+)")(\s+as\s+'?(.+?)'?\.'?(.+?)'?)?/i;
|
|
177
|
-
const tableIdPropSelectionRegex = new RegExp(
|
|
178
|
-
[
|
|
179
|
-
`("(.+)"\\."(.+)")`, // table identifier + property
|
|
180
|
-
`(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`, // optional AS clause
|
|
181
|
-
].join(''),
|
|
182
|
-
'i'
|
|
183
|
-
);
|
|
184
|
-
const noTableIdPropSelectionRegex = /"(.+)"(\s+as\s+'?\1'?)?/i;
|
|
185
|
-
|
|
186
|
-
const tablePropRegex = /"(.+)"\."(.+)"/i;
|
|
187
|
-
const asClauseRegex = /as\s+(.+)$/i;
|
|
188
|
-
const aliasRegex = /as\s+'?(.+)'?\.'?(.+)'?$/i;
|
|
189
|
-
|
|
190
|
-
/* Takes an SQL query as a string, and adds or updates "AS" clauses
|
|
191
|
-
* to the form: `AS 'table_name.column_name'`
|
|
192
|
-
* instead of : `AS "table_name"."column_name"`
|
|
193
|
-
*/
|
|
194
|
-
export function queryAdapter(query: string): string {
|
|
195
|
-
// Things to consider:
|
|
196
|
-
// - need to handle nested selects
|
|
197
|
-
// - what about full joins?
|
|
198
|
-
const selection = selectionRegex.exec(query);
|
|
199
|
-
|
|
200
|
-
if (selection?.length !== 2) {
|
|
201
|
-
return query;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const fields = selection[1].split(',').map((field) => {
|
|
205
|
-
const trimmedField = field.trim();
|
|
206
|
-
|
|
207
|
-
// - different scenarios:
|
|
208
|
-
// - no table identifier + no AS clause -> ignore
|
|
209
|
-
// - no table identifier + AS clause -> ensure AS clause format
|
|
210
|
-
// - table identifier + no AS clause -> add AS clause
|
|
211
|
-
// - table identifier + AS clause -> ensure AS clause format
|
|
212
|
-
const propSelection = tableIdPropSelectionRegex
|
|
213
|
-
.exec(trimmedField)
|
|
214
|
-
?.filter(Boolean);
|
|
215
|
-
|
|
216
|
-
if (!propSelection) {
|
|
217
|
-
return trimmedField;
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
return query.replace(selection[1], fields.join(', '));
|
|
222
|
-
}
|
|
1
|
+
export { aliasFields } from './sql/selection.ts';
|
|
2
|
+
export { mapResultRow } from './sql/result-mapper.ts';
|