@@ -78,7 +78,8 @@ public final class Template {
7878 "minus" ,
7979 "except" ,
8080 "intersect" ,
81- "partition" );
81+ "partition" ,
82+ "within" );
8283 private static final Set <String > BEFORE_TABLE_KEYWORDS
8384 = Set .of ("from" , "join" );
8485 private static final Set <String > FUNCTION_KEYWORDS
@@ -93,6 +94,13 @@ public final class Template {
9394 = Set .of ("first" , "next" );
9495 private static final Set <String > CURRENT_BIGRAMS
9596 = 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" );
96104
97105 private static final String PUNCTUATION = "=><!+-*/()',|&`" ;
98106
@@ -172,6 +180,12 @@ public static String renderWhereStringTemplate(
172180 boolean afterCastAs = false ;
173181 boolean afterFetch = false ;
174182 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 ;
175189
176190 boolean hasMore = tokens .hasMoreTokens ();
177191 String nextToken = hasMore ? tokens .nextToken () : null ;
@@ -232,6 +246,19 @@ else if ( quotedIdentifier && dialect.closeQuote()==token.charAt(0) ) {
232246 final String processedToken ;
233247 final boolean isQuoted =
234248 quoted || quotedIdentifier || isQuoteCharacter ;
249+
250+ // If we're in the post-args region of an ordered-set aggregate, and this
251+ // token is a substantive identifier that's not allowed, end the special handling
252+ if ( !isQuoted && !isWhitespace && afterOrderedSetArgs && isUnqualifiedIdentifier ( token ) ) {
253+ final boolean allowedHere = inListaggExtension
254+ ? LISTAGG_EXTENSION_KEYWORDS .contains ( lcToken )
255+ : "within" .equals ( lcToken );
256+ if ( !allowedHere ) {
257+ afterOrderedSetArgs = false ;
258+ inListaggExtension = false ;
259+ }
260+ }
261+
235262 if ( isQuoted || isWhitespace ) {
236263 processedToken = token ;
237264 }
@@ -245,12 +272,23 @@ else if ( afterFromTable ) {
245272 processedToken = token ;
246273 }
247274 else if ( "(" .equals (lcToken ) ) {
275+ if ( inOrderedSetFunction ) {
276+ orderedSetParenDepth ++;
277+ }
248278 processedToken = token ;
249279 }
250280 else if ( ")" .equals (lcToken ) ) {
251281 inExtractOrTrim = false ;
252282 inCast = false ;
253283 afterCastAs = false ;
284+ if ( inOrderedSetFunction ) {
285+ orderedSetParenDepth --;
286+ if ( orderedSetParenDepth == 0 ) {
287+ inOrderedSetFunction = false ;
288+ afterOrderedSetArgs = true ;
289+ inListaggExtension = lastWasListagg ;
290+ }
291+ }
254292 processedToken = token ;
255293 }
256294 else if ( "," .equals (lcToken ) ) {
@@ -296,13 +334,29 @@ else if ( isCurrent( lcToken, nextToken, sql, symbols, tokens ) ) {
296334 else if ( isBoolean ( lcToken ) ) {
297335 processedToken = dialect .toBooleanValueString ( parseBoolean ( token ) );
298336 }
337+ // Handle ordered-set/LISTAGG post-argument soft keywords (allowed tokens only)
338+ else if ( afterOrderedSetArgs && (inListaggExtension
339+ ? LISTAGG_EXTENSION_KEYWORDS .contains ( lcToken )
340+ : "within" .equals ( lcToken )) ) {
341+ processedToken = token ;
342+ if ( "group" .equals ( lcToken ) ) {
343+ // end special handling after GROUP (inclusive)
344+ afterOrderedSetArgs = false ;
345+ inListaggExtension = false ;
346+ }
347+ }
299348 else if ( isFunctionCall ( nextToken , sql , symbols , tokens ) ) {
300349 if ( FUNCTION_WITH_FROM_KEYWORDS .contains ( lcToken ) ) {
301350 inExtractOrTrim = true ;
302351 }
303352 if ( "cast" .equals ( lcToken ) ) {
304353 inCast = true ;
305354 }
355+ if ( ORDERED_SET_AGGREGATES .contains ( lcToken ) ) {
356+ inOrderedSetFunction = true ;
357+ orderedSetParenDepth = 0 ;
358+ lastWasListagg = "listagg" .equals ( lcToken );
359+ }
306360 processedToken = token ;
307361 }
308362 else if ( isAliasableIdentifier ( token , lcToken , nextToken ,
0 commit comments