1818import net .sf .jsqlparser .JSQLParserException ;
1919import net .sf .jsqlparser .expression .ArrayExpression ;
2020import net .sf .jsqlparser .expression .Expression ;
21+ import net .sf .jsqlparser .parser .AbstractJSqlParser .Dialect ;
2122import net .sf .jsqlparser .parser .CCJSqlParserUtil ;
2223import net .sf .jsqlparser .schema .Table ;
2324import net .sf .jsqlparser .statement .Statement ;
3031import org .junit .jupiter .params .provider .ValueSource ;
3132
3233class TablesNamesFinderTraversalTest {
34+ private static final String SUBQUERY = "(SELECT max(x) FROM t2)" ;
35+
3336 @ ParameterizedTest
3437 @ MethodSource ("nestedTables" )
3538 void findsTablesInNestedChildren (String sql , List <String > expected ) throws JSQLParserException {
@@ -98,7 +101,110 @@ private static Stream<Arguments> nestedTables() {
98101 tables ("UPSERT INTO t1 VALUES (1)" , "t1" ),
99102 tables ("SELECT POSITION('x' IN 'xx') FROM t1" , "t1" ),
100103 tables ("IF a>1 DELETE FROM t3" , "t3" ),
101- tables ("SELECT sum(x) OVER (ORDER BY (SELECT a FROM t2)) FROM t1" , "t1" , "t2" ));
104+ tables ("SELECT sum(x) OVER (ORDER BY (SELECT a FROM t2)) FROM t1" , "t1" , "t2" ),
105+ tables ("SELECT max(a) KEEP (DENSE_RANK FIRST ORDER BY (SELECT x FROM t2)) FROM t1" ,
106+ "t1" , "t2" ),
107+ tables ("SELECT max(a) KEEP (DENSE_RANK FIRST ORDER BY (SELECT x FROM t2)) OVER (PARTITION BY b) FROM t1" ,
108+ "t1" , "t2" ),
109+ tables ("SELECT array_agg(a LIMIT (SELECT x FROM t2)) FROM t1" , "t1" , "t2" ),
110+ tables ("SELECT any_value(a HAVING MAX (SELECT x FROM t2)) FROM t1" , "t1" , "t2" ),
111+ tables ("SELECT any_value(a HAVING MAX (SELECT x FROM t2)) OVER () FROM t1" , "t1" ,
112+ "t2" ),
113+ tables ("SELECT group_concat(a SEPARATOR (SELECT x FROM t2)) FROM t1" , "t1" , "t2" ),
114+ tables ("SELECT group_concat(a ORDER BY (SELECT x FROM t2)) FROM t1" , "t1" , "t2" ),
115+ tables ("SELECT f(a).g((SELECT x FROM t2)) FROM t1" , "t1" , "t2" ),
116+ tables ("SELECT a FROM t1 WHERE (SELECT x FROM t2) IS UNKNOWN" , "t1" , "t2" ),
117+ tables ("SELECT a FROM t1 WHERE MATCH (a) AGAINST ((SELECT x FROM t2))" , "t1" ,
118+ "t2" ),
119+ tables ("SELECT JSON_OBJECTAGG(KEY a VALUE (SELECT x FROM t2)) FROM t1" , "t1" ,
120+ "t2" ),
121+ tables ("SELECT JSON_ARRAYAGG(a ORDER BY (SELECT x FROM t2)) FROM t1" , "t1" , "t2" ),
122+ tables ("SELECT JSON_ARRAYAGG(a) OVER (PARTITION BY (SELECT x FROM t2) ORDER BY (SELECT y FROM t3)) FROM t1" ,
123+ "t1" , "t2" , "t3" ),
124+ tables ("SELECT JSON_ARRAYAGG(a) OVER (ORDER BY b ROWS (SELECT x FROM t2) PRECEDING) FROM t1" ,
125+ "t1" , "t2" ),
126+ tables ("SELECT * FROM XMLTABLE('/r' PASSING (SELECT x FROM t2) COLUMNS a int PATH 'a') x" ,
127+ "t2" ),
128+ tables ("SELECT * FROM JSON_TABLE((SELECT x FROM t2), '$' COLUMNS (a int PATH '$.a')) j" ,
129+ "t2" ),
130+ tables ("SELECT * FROM generate_series(1, (SELECT max(x) FROM t2)) g" , "t2" ));
131+ }
132+
133+ @ ParameterizedTest
134+ @ MethodSource ("postgresTables" )
135+ void findsTablesInPostgresExpressionChildren (String sql , List <String > expected )
136+ throws JSQLParserException {
137+ Statement statement = CCJSqlParserUtil .parse (sql ,
138+ parser -> parser .withDialect (Dialect .POSTGRESQL ));
139+ assertThat (new TablesNamesFinder <>().getTables (statement ))
140+ .containsExactlyInAnyOrderElementsOf (expected );
141+ assertThat (new TablesNamesFinder <>().getTablesOrOtherSources (statement ))
142+ .containsAll (expected );
143+ }
144+
145+ private static Stream <Arguments > postgresTables () {
146+ return Stream .of (
147+ // aggregate ORDER BY inside the call
148+ tables ("SELECT string_agg(a, ',' ORDER BY " + SUBQUERY + ") FROM t1" , "t1" ,
149+ "t2" ),
150+ tables ("SELECT array_agg(a ORDER BY " + SUBQUERY + ") FROM t1" , "t1" ,
151+ "t2" ),
152+ // LIKE ... ESCAPE
153+ tables ("SELECT a FROM t1 WHERE a LIKE 'a' ESCAPE " + SUBQUERY , "t1" ,
154+ "t2" ),
155+ // AT TIME ZONE, zone operand
156+ tables ("SELECT now() AT TIME ZONE " + SUBQUERY , "t2" ),
157+ // JSON operators, right operand
158+ tables ("SELECT j -> " + SUBQUERY + " FROM t1" , "t1" , "t2" ),
159+ tables ("SELECT j ->> " + SUBQUERY + " FROM t1" , "t1" , "t2" ),
160+ tables ("SELECT j #> ARRAY[" + SUBQUERY + "] FROM t1" , "t1" , "t2" ),
161+ tables ("SELECT j #>> ARRAY[" + SUBQUERY + "] FROM t1" , "t1" , "t2" ),
162+ // array slice bounds and subscript
163+ tables ("SELECT a[1:(SELECT count(*)::int FROM t2)] FROM t1" , "t1" ,
164+ "t2" ),
165+ tables ("SELECT a[(SELECT count(*)::int FROM t2):2] FROM t1" , "t1" ,
166+ "t2" ),
167+ tables ("SELECT a[(SELECT count(*)::int FROM t2)] FROM t1" , "t1" ,
168+ "t2" ),
169+ // positions that were already traversed
170+ tables ("SELECT f(" + SUBQUERY + ") FROM t1" , "t1" , "t2" ),
171+ tables ("SELECT count(*) FILTER (WHERE a = " + SUBQUERY + ") FROM t1" , "t1" ,
172+ "t2" ),
173+ tables ("SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY " + SUBQUERY
174+ + ") FROM t1" , "t1" , "t2" ),
175+ tables ("SELECT sum(a) OVER (PARTITION BY " + SUBQUERY + ") FROM t1" , "t1" ,
176+ "t2" ),
177+ tables ("SELECT sum(a) OVER (ORDER BY " + SUBQUERY + ") FROM t1" , "t1" ,
178+ "t2" ),
179+ tables ("SELECT sum(a) OVER (ORDER BY b ROWS BETWEEN " + SUBQUERY
180+ + " PRECEDING AND CURRENT ROW) FROM t1" , "t1" , "t2" ),
181+ tables ("SELECT a FROM t1 LIMIT " + SUBQUERY , "t1" , "t2" ),
182+ tables ("SELECT a FROM t1 OFFSET " + SUBQUERY , "t1" , "t2" ),
183+ tables ("SELECT a FROM t1 FETCH FIRST " + SUBQUERY + " ROWS ONLY" , "t1" ,
184+ "t2" ),
185+ tables ("SELECT a FROM t1 WHERE a = ANY (SELECT x FROM t2)" , "t1" ,
186+ "t2" ),
187+ tables ("SELECT a FROM t1 WHERE a > ALL (SELECT x FROM t2)" , "t1" ,
188+ "t2" ),
189+ tables ("SELECT a FROM t1 WHERE a = SOME (SELECT x FROM t2)" , "t1" ,
190+ "t2" ),
191+ tables ("SELECT ARRAY(SELECT x FROM t2)" , "t2" ),
192+ tables ("SELECT * FROM (VALUES (" + SUBQUERY + ")) v(x)" , "t2" ),
193+ tables ("SELECT substring(a FROM " + SUBQUERY + ") FROM t1" , "t1" , "t2" ),
194+ tables ("SELECT trim(BOTH " + SUBQUERY + " FROM a) FROM t1" , "t1" , "t2" ),
195+ tables ("SELECT position(" + SUBQUERY + " IN a) FROM t1" , "t1" , "t2" ),
196+ tables ("SELECT overlay(a PLACING " + SUBQUERY + " FROM 1) FROM t1" , "t1" ,
197+ "t2" ),
198+ tables ("SELECT CASE WHEN a = 1 THEN " + SUBQUERY + " END FROM t1" , "t1" ,
199+ "t2" ),
200+ tables ("SELECT CAST(" + SUBQUERY + " AS text) FROM t1" , "t1" , "t2" ),
201+ tables ("SELECT " + SUBQUERY + "::text FROM t1" , "t1" , "t2" ),
202+ tables ("SELECT count(*) FROM t1 GROUP BY " + SUBQUERY , "t1" , "t2" ),
203+ tables ("SELECT count(*) FROM t1 GROUP BY GROUPING SETS ((" + SUBQUERY + "))" , "t1" ,
204+ "t2" ),
205+ tables ("SELECT DISTINCT ON (" + SUBQUERY + ") a FROM t1" , "t1" , "t2" ),
206+ tables ("SELECT * FROM t1, LATERAL (SELECT x FROM t2) p" , "t1" ,
207+ "t2" ));
102208 }
103209
104210 private static Arguments tables (String sql , String ... names ) {
@@ -122,7 +228,12 @@ private static Stream<Arguments> expressionTables() {
122228 tables ("f()[OFFSET((SELECT x FROM t2)):OFFSET((SELECT y FROM t3))]" , "t2" , "t3" ),
123229 tables ("POSITION('x' IN (SELECT a FROM t2))" , "t2" ),
124230 tables ("sum(x) OVER (PARTITION BY (SELECT a FROM t2))" , "t2" ),
125- tables ("a[1]" ), tables ("f()[1]" ), tables ("f()[:2]" ), tables ("f()[:]" ));
231+ tables ("string_agg(a, ',' ORDER BY (SELECT x FROM t2))" , "t2" ),
232+ tables ("a LIKE 'a' ESCAPE (SELECT x FROM t2)" , "t2" ),
233+ tables ("now() AT TIME ZONE (SELECT x FROM t2)" , "t2" ),
234+ tables ("(SELECT x FROM t2) IS UNKNOWN" , "t2" ),
235+ tables ("a[1]" ), tables ("f()[1]" ), tables ("f()[:2]" ), tables ("f()[:]" ),
236+ tables ("f(a).b.c" ));
126237 }
127238
128239 @ ParameterizedTest
@@ -164,6 +275,16 @@ void visitsNewChildrenWithoutRepeatingExistingChildren() throws JSQLParserExcept
164275 assertThat (finder .visits ).isEqualTo (Map .of ("t1" , 1 , "t2" , 1 , "t3" , 1 , "t4" , 1 , "t5" , 1 ));
165276 }
166277
278+ @ Test
279+ void visitsAggregateChildrenOnce () throws JSQLParserException {
280+ CountingFinder finder = new CountingFinder ();
281+ Statement statement = CCJSqlParserUtil .parse (
282+ "SELECT max(a) KEEP (DENSE_RANK FIRST ORDER BY (SELECT x FROM t2)) OVER (PARTITION BY (SELECT y FROM t3)), string_agg(a, ',' ORDER BY (SELECT z FROM t4)) FROM t1" );
283+ assertThat (finder .getTables (statement )).containsExactlyInAnyOrder ("t1" , "t2" , "t3" ,
284+ "t4" );
285+ assertThat (finder .visits ).isEqualTo (Map .of ("t1" , 1 , "t2" , 1 , "t3" , 1 , "t4" , 1 ));
286+ }
287+
167288 @ Test
168289 void resetsStateBetweenStatementAndExpressionVisits () throws JSQLParserException {
169290 TablesNamesFinder <?> finder = new TablesNamesFinder <>();
0 commit comments