1- import ast from '../test-utils/utils/asts' ;
1+ import * as t from '../test-utils/meta' ;
2+ import { SelectStmt } from '@pgsql/types' ;
23import { generateTsAstCodeFromPgAst } from '../src/utils'
34import generate from '@babel/generator' ;
45
56it ( '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