Skip to content

Commit ca1d705

Browse files
author
Mike Mannion
committed
HHH-19704
- Enhance Template so it works with LISTAGG function and its variants - Provides test cases for LISTAGG function variants - Enhance code coverage of Template in general
1 parent 545c0a4 commit ca1d705

File tree

2 files changed

+413
-1
lines changed

2 files changed

+413
-1
lines changed

hibernate-core/src/main/java/org/hibernate/sql/Template.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)