@@ -78,7 +78,8 @@ public final class Template {
78
78
"minus" ,
79
79
"except" ,
80
80
"intersect" ,
81
- "partition" );
81
+ "partition" ,
82
+ "within" );
82
83
private static final Set <String > BEFORE_TABLE_KEYWORDS
83
84
= Set .of ("from" , "join" );
84
85
private static final Set <String > FUNCTION_KEYWORDS
@@ -93,6 +94,13 @@ public final class Template {
93
94
= Set .of ("first" , "next" );
94
95
private static final Set <String > CURRENT_BIGRAMS
95
96
= Set .of ("date" , "time" , "timestamp" );
97
+ // Ordered-set aggregate function names we want to recognize
98
+ private static final Set <String > ORDERED_SET_AGGREGATES
99
+ = Set .of ("listagg" , "percentile_cont" , "percentile_disc" , "mode" );
100
+ // Soft keywords that are only treated as keywords in the LISTAGG extension immediately
101
+ // following the argument list and up to and including GROUP
102
+ private static final Set <String > LISTAGG_EXTENSION_KEYWORDS
103
+ = Set .of ("on" , "overflow" , "error" , "truncate" , "without" , "count" , "within" , "with" , "group" );
96
104
97
105
private static final String PUNCTUATION = "=><!+-*/()',|&`" ;
98
106
@@ -172,6 +180,12 @@ public static String renderWhereStringTemplate(
172
180
boolean afterCastAs = false ;
173
181
boolean afterFetch = false ;
174
182
boolean afterCurrent = false ;
183
+ // State for ordered-set aggregates / LISTAGG extension handling
184
+ boolean inOrderedSetFunction = false ;
185
+ int orderedSetParenDepth = 0 ;
186
+ boolean afterOrderedSetArgs = false ;
187
+ boolean inListaggExtension = false ;
188
+ boolean lastWasListagg = false ;
175
189
176
190
boolean hasMore = tokens .hasMoreTokens ();
177
191
String nextToken = hasMore ? tokens .nextToken () : null ;
@@ -232,6 +246,7 @@ else if ( quotedIdentifier && dialect.closeQuote()==token.charAt(0) ) {
232
246
final String processedToken ;
233
247
final boolean isQuoted =
234
248
quoted || quotedIdentifier || isQuoteCharacter ;
249
+
235
250
if ( isQuoted || isWhitespace ) {
236
251
processedToken = token ;
237
252
}
@@ -245,12 +260,23 @@ else if ( afterFromTable ) {
245
260
processedToken = token ;
246
261
}
247
262
else if ( "(" .equals (lcToken ) ) {
263
+ if ( inOrderedSetFunction ) {
264
+ orderedSetParenDepth ++;
265
+ }
248
266
processedToken = token ;
249
267
}
250
268
else if ( ")" .equals (lcToken ) ) {
251
269
inExtractOrTrim = false ;
252
270
inCast = false ;
253
271
afterCastAs = false ;
272
+ if ( inOrderedSetFunction ) {
273
+ orderedSetParenDepth --;
274
+ if ( orderedSetParenDepth == 0 ) {
275
+ inOrderedSetFunction = false ;
276
+ afterOrderedSetArgs = true ;
277
+ inListaggExtension = lastWasListagg ;
278
+ }
279
+ }
254
280
processedToken = token ;
255
281
}
256
282
else if ( "," .equals (lcToken ) ) {
@@ -303,11 +329,31 @@ else if ( isFunctionCall( nextToken, sql, symbols, tokens ) ) {
303
329
if ( "cast" .equals ( lcToken ) ) {
304
330
inCast = true ;
305
331
}
332
+ if ( ORDERED_SET_AGGREGATES .contains ( lcToken ) ) {
333
+ inOrderedSetFunction = true ;
334
+ orderedSetParenDepth = 0 ;
335
+ lastWasListagg = "listagg" .equals ( lcToken );
336
+ }
337
+ processedToken = token ;
338
+ }
339
+ else if ( afterOrderedSetArgs && (inListaggExtension
340
+ ? ( LISTAGG_EXTENSION_KEYWORDS .contains ( lcToken ) )
341
+ : "within" .equals ( lcToken )) ) {
342
+ if ( "group" .equals ( lcToken ) ) {
343
+ // end special handling after GROUP (inclusive)
344
+ afterOrderedSetArgs = false ;
345
+ inListaggExtension = false ;
346
+ }
306
347
processedToken = token ;
307
348
}
308
349
else if ( isAliasableIdentifier ( token , lcToken , nextToken ,
309
350
sql , symbols , tokens , wasAfterCurrent ,
310
351
dialect , typeConfiguration ) ) {
352
+ // Any aliasable identifier here cannot be one of the soft keywords allowed in the
353
+ // ordered-set/LISTAGG post-args region. We've left that region so must end special handling.
354
+ // (It's irrelevant at this point whether the dialect supports ordered-set/LISTAGG.)
355
+ afterOrderedSetArgs = false ;
356
+ inListaggExtension = false ;
311
357
processedToken = alias + '.' + dialect .quote (token );
312
358
}
313
359
else {
0 commit comments