@leonardovida-md/drizzle-neo-duckdb 1.2.0 → 1.2.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.
@@ -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
+ }