Skip to content

Commit 35da98b

Browse files
authored
Merge pull request #158 from launchql/feat/meta
meta setup
2 parents cffd419 + c8b4833 commit 35da98b

File tree

8 files changed

+19082
-5183
lines changed

8 files changed

+19082
-5183
lines changed

packages/proto-parser/__tests__/__snapshots__/meta.test.ts.snap

Lines changed: 1233 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 350 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
import ast from '../test-utils/utils/asts';
1+
import * as t from '../test-utils/meta';
2+
import { SelectStmt } from '@pgsql/types';
23
import { generateTsAstCodeFromPgAst } from '../src/utils'
34
import generate from '@babel/generator';
45

56
it('AST to AST to create AST — meta 🤯', () => {
6-
const selectStmt = ast.selectStmt({
7+
const selectStmt = t.nodes.selectStmt({
78
targetList: [
8-
ast.resTarget({
9-
val: ast.columnRef({
10-
fields: [ast.aStar()]
9+
t.nodes.resTarget({
10+
val: t.nodes.columnRef({
11+
fields: [t.nodes.aStar()]
1112
})
1213
})
1314
],
1415
fromClause: [
15-
ast.rangeVar({
16+
t.nodes.rangeVar({
1617
relname: 'some_amazing_table',
1718
inh: true,
1819
relpersistence: 'p'
@@ -26,20 +27,348 @@ it('AST to AST to create AST — meta 🤯', () => {
2627

2728
const astForAst = generateTsAstCodeFromPgAst(selectStmt);
2829
expect(generate(astForAst).code).toMatchSnapshot();
29-
expect(generate(astForAst).code).toEqual(
30-
`ast.selectStmt({
31-
targetList: [ast.resTarget({
32-
val: ast.columnRef({
33-
fields: [ast.aStar({})]
34-
})
35-
})],
36-
fromClause: [ast.rangeVar({
37-
relname: "some_amazing_table",
38-
inh: true,
39-
relpersistence: "p"
40-
})],
41-
limitOption: "LIMIT_OPTION_DEFAULT",
42-
op: "SETOP_NONE"
43-
})`);
30+
});
4431

32+
it('Complex AST — Advanced SQL with CTEs, Window Functions, Joins, and Subqueries', () => {
33+
const complexSelectStmt: { SelectStmt: SelectStmt } = t.nodes.selectStmt({
34+
// WITH clause for CTEs
35+
withClause: t.ast.withClause({
36+
ctes: [
37+
// First CTE: Sales summary
38+
t.nodes.commonTableExpr({
39+
ctename: 'sales_summary',
40+
ctequery: t.nodes.selectStmt({
41+
targetList: [
42+
t.nodes.resTarget({
43+
name: 'customer_id',
44+
val: t.nodes.columnRef({
45+
fields: [t.nodes.string({ sval: 'customer_id' })]
46+
})
47+
}),
48+
t.nodes.resTarget({
49+
name: 'total_sales',
50+
val: t.nodes.funcCall({
51+
funcname: [t.nodes.string({ sval: 'sum' })],
52+
args: [
53+
t.nodes.columnRef({
54+
fields: [t.nodes.string({ sval: 'amount' })]
55+
})
56+
]
57+
})
58+
}),
59+
t.nodes.resTarget({
60+
name: 'avg_order_value',
61+
val: t.nodes.funcCall({
62+
funcname: [t.nodes.string({ sval: 'avg' })],
63+
args: [
64+
t.nodes.columnRef({
65+
fields: [t.nodes.string({ sval: 'amount' })]
66+
})
67+
]
68+
})
69+
})
70+
],
71+
fromClause: [
72+
t.nodes.rangeVar({
73+
relname: 'orders',
74+
inh: true,
75+
relpersistence: 'p'
76+
})
77+
],
78+
whereClause: t.nodes.aExpr({
79+
kind: 'AEXPR_OP',
80+
name: [t.nodes.string({ sval: '>=' })],
81+
lexpr: t.nodes.columnRef({
82+
fields: [t.nodes.string({ sval: 'order_date' })]
83+
}),
84+
rexpr: t.nodes.aConst({
85+
sval: t.ast.string({ sval: '2023-01-01' })
86+
})
87+
}),
88+
groupClause: [
89+
t.nodes.columnRef({
90+
fields: [t.nodes.string({ sval: 'customer_id' })]
91+
})
92+
],
93+
limitOption: 'LIMIT_OPTION_DEFAULT',
94+
op: 'SETOP_NONE'
95+
})
96+
}),
97+
t.nodes.commonTableExpr({
98+
ctename: 'customer_rankings',
99+
ctequery: t.nodes.selectStmt({
100+
targetList: [
101+
t.nodes.resTarget({
102+
name: 'customer_id',
103+
val: t.nodes.columnRef({
104+
fields: [t.nodes.string({ sval: 'customer_id' })]
105+
})
106+
}),
107+
t.nodes.resTarget({
108+
name: 'total_sales',
109+
val: t.nodes.columnRef({
110+
fields: [t.nodes.string({ sval: 'total_sales' })]
111+
})
112+
}),
113+
t.nodes.resTarget({
114+
name: 'sales_rank',
115+
val: t.nodes.windowFunc({
116+
winfnoid: 3133, // ROW_NUMBER function OID
117+
wintype: 20, // INT8 type
118+
args: [],
119+
winref: 1,
120+
winstar: false,
121+
winagg: false
122+
})
123+
}),
124+
t.nodes.resTarget({
125+
name: 'sales_percentile',
126+
val: t.nodes.windowFunc({
127+
winfnoid: 3974, // PERCENT_RANK function OID
128+
wintype: 701, // FLOAT8 type
129+
args: [],
130+
winref: 2,
131+
winstar: false,
132+
winagg: false
133+
})
134+
})
135+
],
136+
fromClause: [
137+
t.nodes.rangeVar({
138+
relname: 'sales_summary',
139+
inh: true,
140+
relpersistence: 'p'
141+
})
142+
],
143+
windowClause: [
144+
t.nodes.windowDef({
145+
name: 'sales_window',
146+
orderClause: [
147+
t.nodes.sortBy({
148+
node: t.nodes.columnRef({
149+
fields: [t.nodes.string({ sval: 'total_sales' })]
150+
}),
151+
sortby_dir: 'SORTBY_DESC',
152+
sortby_nulls: 'SORTBY_NULLS_DEFAULT'
153+
})
154+
]
155+
})
156+
],
157+
limitOption: 'LIMIT_OPTION_DEFAULT',
158+
op: 'SETOP_NONE'
159+
})
160+
})
161+
],
162+
recursive: false
163+
}),
164+
165+
// Main SELECT target list with complex expressions
166+
targetList: [
167+
t.nodes.resTarget({
168+
name: 'customer_name',
169+
val: t.nodes.columnRef({
170+
fields: [t.nodes.string({ sval: 'c' }), t.nodes.string({ sval: 'name' })]
171+
})
172+
}),
173+
t.nodes.resTarget({
174+
name: 'total_sales',
175+
val: t.nodes.columnRef({
176+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'total_sales' })]
177+
})
178+
}),
179+
t.nodes.resTarget({
180+
name: 'sales_rank',
181+
val: t.nodes.columnRef({
182+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'sales_rank' })]
183+
})
184+
}),
185+
t.nodes.resTarget({
186+
name: 'customer_tier',
187+
val: t.nodes.caseExpr({
188+
args: [
189+
t.nodes.caseWhen({
190+
expr: t.nodes.aExpr({
191+
kind: 'AEXPR_OP',
192+
name: [t.nodes.string({ sval: '<=' })],
193+
lexpr: t.nodes.columnRef({
194+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'sales_rank' })]
195+
}),
196+
rexpr: t.nodes.aConst({
197+
ival: t.ast.integer({ ival: 10 })
198+
})
199+
}),
200+
result: t.nodes.aConst({
201+
sval: t.ast.string({ sval: 'Premium' })
202+
})
203+
}),
204+
t.nodes.caseWhen({
205+
expr: t.nodes.aExpr({
206+
kind: 'AEXPR_OP',
207+
name: [t.nodes.string({ sval: '<=' })],
208+
lexpr: t.nodes.columnRef({
209+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'sales_rank' })]
210+
}),
211+
rexpr: t.nodes.aConst({
212+
ival: t.ast.integer({ ival: 50 })
213+
})
214+
}),
215+
result: t.nodes.aConst({
216+
sval: t.ast.string({ sval: 'Gold' })
217+
})
218+
}),
219+
t.nodes.caseWhen({
220+
expr: t.nodes.aExpr({
221+
kind: 'AEXPR_OP',
222+
name: [t.nodes.string({ sval: '<=' })],
223+
lexpr: t.nodes.columnRef({
224+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'sales_rank' })]
225+
}),
226+
rexpr: t.nodes.aConst({
227+
ival: t.ast.integer({ ival: 100 })
228+
})
229+
}),
230+
result: t.nodes.aConst({
231+
sval: t.ast.string({ sval: 'Silver' })
232+
})
233+
})
234+
],
235+
defresult: t.nodes.aConst({
236+
sval: t.ast.string({ sval: 'Bronze' })
237+
})
238+
})
239+
}),
240+
t.nodes.resTarget({
241+
name: 'recent_order_count',
242+
val: t.nodes.subLink({
243+
subLinkType: 'EXPR_SUBLINK',
244+
subselect: t.nodes.selectStmt({
245+
targetList: [
246+
t.nodes.resTarget({
247+
val: t.nodes.funcCall({
248+
funcname: [t.nodes.string({ sval: 'count' })],
249+
args: [t.nodes.aStar()]
250+
})
251+
})
252+
],
253+
fromClause: [
254+
t.nodes.rangeVar({
255+
relname: 'orders',
256+
alias: t.ast.alias({ aliasname: 'o2' }),
257+
inh: true,
258+
relpersistence: 'p'
259+
})
260+
],
261+
whereClause: t.nodes.boolExpr({
262+
boolop: 'AND_EXPR',
263+
args: [
264+
t.nodes.aExpr({
265+
kind: 'AEXPR_OP',
266+
name: [t.nodes.string({ sval: '=' })],
267+
lexpr: t.nodes.columnRef({
268+
fields: [t.nodes.string({ sval: 'o2' }), t.nodes.string({ sval: 'customer_id' })]
269+
}),
270+
rexpr: t.nodes.columnRef({
271+
fields: [t.nodes.string({ sval: 'c' }), t.nodes.string({ sval: 'id' })]
272+
})
273+
}),
274+
t.nodes.aExpr({
275+
kind: 'AEXPR_OP',
276+
name: [t.nodes.string({ sval: '>=' })],
277+
lexpr: t.nodes.columnRef({
278+
fields: [t.nodes.string({ sval: 'o2' }), t.nodes.string({ sval: 'order_date' })]
279+
}),
280+
rexpr: t.nodes.funcCall({
281+
funcname: [t.nodes.string({ sval: 'current_date' })],
282+
args: []
283+
})
284+
})
285+
]
286+
}),
287+
limitOption: 'LIMIT_OPTION_DEFAULT',
288+
op: 'SETOP_NONE'
289+
})
290+
})
291+
})
292+
],
293+
294+
// Complex FROM clause with joins
295+
fromClause: [
296+
t.nodes.joinExpr({
297+
jointype: 'JOIN_INNER',
298+
larg: t.nodes.rangeVar({
299+
relname: 'customers',
300+
alias: t.ast.alias({ aliasname: 'c' }),
301+
inh: true,
302+
relpersistence: 'p'
303+
}),
304+
rarg: t.nodes.rangeVar({
305+
relname: 'customer_rankings',
306+
alias: t.ast.alias({ aliasname: 'cr' }),
307+
inh: true,
308+
relpersistence: 'p'
309+
}),
310+
quals: t.nodes.aExpr({
311+
kind: 'AEXPR_OP',
312+
name: [t.nodes.string({ sval: '=' })],
313+
lexpr: t.nodes.columnRef({
314+
fields: [t.nodes.string({ sval: 'c' }), t.nodes.string({ sval: 'id' })]
315+
}),
316+
rexpr: t.nodes.columnRef({
317+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'customer_id' })]
318+
})
319+
})
320+
})
321+
],
322+
323+
// WHERE clause with complex conditions
324+
whereClause: t.nodes.boolExpr({
325+
boolop: 'AND_EXPR',
326+
args: [
327+
t.nodes.aExpr({
328+
kind: 'AEXPR_OP',
329+
name: [t.nodes.string({ sval: '>' })],
330+
lexpr: t.nodes.columnRef({
331+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'total_sales' })]
332+
}),
333+
rexpr: t.nodes.aConst({
334+
ival: t.ast.integer({ ival: 1000 })
335+
})
336+
}),
337+
t.nodes.aExpr({
338+
kind: 'AEXPR_OP',
339+
name: [t.nodes.string({ sval: 'IS NOT' })],
340+
lexpr: t.nodes.columnRef({
341+
fields: [t.nodes.string({ sval: 'c' }), t.nodes.string({ sval: 'status' })]
342+
}),
343+
rexpr: t.nodes.aConst({
344+
sval: t.ast.string({ sval: 'inactive' })
345+
})
346+
})
347+
]
348+
}),
349+
350+
// ORDER BY clause
351+
sortClause: [
352+
t.nodes.sortBy({
353+
node: t.nodes.columnRef({
354+
fields: [t.nodes.string({ sval: 'cr' }), t.nodes.string({ sval: 'sales_rank' })]
355+
}),
356+
sortby_dir: 'SORTBY_ASC',
357+
sortby_nulls: 'SORTBY_NULLS_DEFAULT'
358+
})
359+
],
360+
361+
// LIMIT clause
362+
limitCount: t.nodes.aConst({
363+
ival: t.ast.integer({ ival: 50 })
364+
}),
365+
366+
limitOption: 'LIMIT_OPTION_COUNT',
367+
op: 'SETOP_NONE'
368+
});
369+
370+
expect(complexSelectStmt).toMatchSnapshot();
371+
372+
const astForComplexAst = generateTsAstCodeFromPgAst(complexSelectStmt);
373+
expect(generate(astForComplexAst).code).toMatchSnapshot();
45374
});

0 commit comments

Comments
 (0)