Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

HHH-19704 listagg on overflow #10759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
michaelfranz wants to merge 12 commits into hibernate:main
base: main
Choose a base branch
Loading
from michaelfranz:HHH-19704-listagg-on-overflow
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
aa6fb4d
HHH-19704 Added handling of LISTAGG function variants.
Aug 16, 2025
abe6006
HHH-19704 Added handling of LISTAGG function.
Aug 16, 2025
39e9aec
Merge remote-tracking branch 'origin/HHH-19704-listagg-on-overflow' i...
Aug 17, 2025
1bac432
HHH-19704 Removed redundant param.
Aug 17, 2025
40843a1
HHH-19704 Added handling of LISTAGG function.
Aug 16, 2025
06376b4
Merge remote-tracking branch 'origin/HHH-19704-listagg-on-overflow' i...
Aug 20, 2025
64aefc0
HHH-19704 Merge branch 'main' into HHH-19704-listagg-on-overflow
Aug 20, 2025
e09c805
HHH-19704 redundant KW
Aug 20, 2025
fae7897
Merge branch 'main' into HHH-19704-listagg-on-overflow
Aug 21, 2025
8180eb8
Merge remote-tracking branch 'upstream/main' into HHH-19704-listagg-o...
Aug 27, 2025
1f42834
HHH-19704 Merge branch 'main' into HHH-19704-listagg-on-overflow
Sep 4, 2025
d48ceda
HHH-19704 Merge remote-tracking branch 'upstream/main' into HHH-19704...
Sep 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions hibernate-core/src/main/java/org/hibernate/sql/Template.java
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ public final class Template {
= Set.of("first", "next");
private static final Set<String> CURRENT_BIGRAMS
= Set.of("date", "time", "timestamp");
// Ordered-set aggregate function names we want to recognize
private static final Set<String> ORDERED_SET_AGGREGATES
= Set.of("listagg", "percentile_cont", "percentile_disc", "mode");
// Soft keywords that are only treated as keywords in the LISTAGG extension immediately
// following the argument list and up to and including GROUP
private static final Set<String> LISTAGG_EXTENSION_KEYWORDS
= Set.of("on", "overflow", "error", "truncate", "without", "count", "within", "with", "group");

private static final String PUNCTUATION = "=><!+-*/()',|&`";

Expand Down Expand Up @@ -173,6 +180,12 @@ public static String renderWhereStringTemplate(
int inExtractOrTrim = -1;
int inCast = -1;
int nestingLevel = 0;
// State for ordered-set aggregates / LISTAGG extension handling
boolean inOrderedSetFunction = false;
int orderedSetParenDepth = 0;
boolean afterOrderedSetArgs = false;
boolean inListaggExtension = false;
boolean lastWasListagg = false;

boolean hasMore = tokens.hasMoreTokens();
String nextToken = hasMore ? tokens.nextToken() : null;
Expand Down Expand Up @@ -216,8 +229,8 @@ else if ( quotedIdentifier && dialect.closeQuote()==token.charAt(0) ) {
isOpenQuote = false;
}
if ( isOpenQuote
&& !inFromClause // don't want to append alias to tokens inside the FROM clause
&& !endsWithDot( previousToken ) ) {
&& !inFromClause // don't want to append alias to tokens inside the FROM clause
&& !endsWithDot( previousToken ) ) {
result.append( alias ).append( '.' );
}
}
Expand Down Expand Up @@ -246,6 +259,9 @@ else if ( afterFromTable ) {
processedToken = token;
}
else if ( "(".equals(lcToken) ) {
if ( inOrderedSetFunction ) {
orderedSetParenDepth++;
}
nestingLevel ++;
processedToken = token;
}
Expand All @@ -258,6 +274,14 @@ else if ( ")".equals(lcToken) ) {
inCast = -1;
afterCastAs = false;
}
if ( inOrderedSetFunction ) {
orderedSetParenDepth--;
if ( orderedSetParenDepth == 0 ) {
inOrderedSetFunction = false;
afterOrderedSetArgs = true;
inListaggExtension = lastWasListagg;
}
}
processedToken = token;
}
else if ( ",".equals(lcToken) ) {
Expand Down Expand Up @@ -310,11 +334,31 @@ else if ( isFunctionCall( nextToken, sql, symbols, tokens ) ) {
if ( "cast".equals( lcToken ) ) {
inCast = nestingLevel;
}
if ( ORDERED_SET_AGGREGATES.contains( lcToken ) ) {
inOrderedSetFunction = true;
orderedSetParenDepth = 0;
lastWasListagg = "listagg".equals( lcToken );
}
processedToken = token;
}
else if ( afterOrderedSetArgs && (inListaggExtension
? ( LISTAGG_EXTENSION_KEYWORDS.contains( lcToken ) )
: "within".equals( lcToken )) ) {
if ( "group".equals( lcToken ) ) {
// end special handling after GROUP (inclusive)
afterOrderedSetArgs = false;
inListaggExtension = false;
}
processedToken = token;
}
else if ( isAliasableIdentifier( token, lcToken, nextToken,
sql, symbols, tokens, wasAfterCurrent,
dialect, typeConfiguration ) ) {
// Any aliasable identifier here cannot be one of the soft keywords allowed in the
// ordered-set/LISTAGG post-args region. We've left that region so must end special handling.
// (It's irrelevant at this point whether the dialect supports ordered-set/LISTAGG.)
afterOrderedSetArgs = false;
inListaggExtension = false;
processedToken = alias + '.' + dialect.quote(token);
}
else {
Expand All @@ -325,8 +369,8 @@ else if ( isAliasableIdentifier( token, lcToken, nextToken,

//Yuck:
if ( inFromClause
&& KEYWORDS.contains( lcToken ) // "as" is not in KEYWORDS
&& !BEFORE_TABLE_KEYWORDS.contains( lcToken ) ) {
&& KEYWORDS.contains( lcToken ) // "as" is not in KEYWORDS
&& !BEFORE_TABLE_KEYWORDS.contains( lcToken ) ) {
inFromClause = false;
}
}
Expand All @@ -340,8 +384,8 @@ private static boolean isAliasableIdentifier(
boolean wasAfterCurrent,
Dialect dialect, TypeConfiguration typeConfiguration) {
return isUnqualifiedIdentifier( token )
&& !isKeyword( lcToken, wasAfterCurrent, dialect, typeConfiguration )
&& !isLiteral( lcToken, nextToken, sql, symbols, tokens );
&& !isKeyword( lcToken, wasAfterCurrent, dialect, typeConfiguration )
&& !isLiteral( lcToken, nextToken, sql, symbols, tokens );
}

private static boolean isFunctionCall(
Expand All @@ -361,13 +405,13 @@ private static boolean isCurrent(
String lcToken, String nextToken,
String sql, String symbols, StringTokenizer tokens) {
return "current".equals( lcToken )
&& nextToken.isBlank()
&& lookPastBlankTokens( sql, symbols, tokens, 1, CURRENT_BIGRAMS::contains );
&& nextToken.isBlank()
&& lookPastBlankTokens( sql, symbols, tokens, 1, CURRENT_BIGRAMS::contains );
}

private static boolean isFetch(Dialect dialect, String lcToken) {
return "fetch".equals( lcToken )
&& dialect.getKeywords().contains( "fetch" );
&& dialect.getKeywords().contains( "fetch" );
}

private static boolean endsWithDot(String token) {
Expand All @@ -386,9 +430,9 @@ else if ( LITERAL_PREFIXES.contains( lcToken ) ) {
// to find the first non-blank token
return lookPastBlankTokens( sqlWhereString, symbols, tokens, 1,
nextToken -> "'".equals(nextToken)
|| lcToken.equals("time") && "with".equals(nextToken)
|| lcToken.equals("timestamp") && "with".equals(nextToken)
|| lcToken.equals("time") && "zone".equals(nextToken) );
|| lcToken.equals("time") && "with".equals(nextToken)
|| lcToken.equals("timestamp") && "with".equals(nextToken)
|| lcToken.equals("time") && "zone".equals(nextToken) );
}
else {
return "'".equals(next);
Expand Down Expand Up @@ -480,9 +524,9 @@ private static boolean isKeyword(
}
else {
return KEYWORDS.contains( lcToken )
|| isType( lcToken, typeConfiguration )
|| dialect.getKeywords().contains( lcToken )
|| FUNCTION_KEYWORDS.contains( lcToken );
|| isType( lcToken, typeConfiguration )
|| dialect.getKeywords().contains( lcToken )
|| FUNCTION_KEYWORDS.contains( lcToken );
}
}

Expand All @@ -493,8 +537,8 @@ private static boolean isType(String lcToken, TypeConfiguration typeConfiguratio
private static boolean isUnqualifiedIdentifier(String token) {
final char initialChar = token.charAt( 0 );
return initialChar == '`' // allow any identifier quoted with backtick
|| isLetter( initialChar ) // only recognizes identifiers beginning with a letter
&& token.indexOf( '.' ) < 0; // don't qualify already-qualified identifiers
|| isLetter( initialChar ) // only recognizes identifiers beginning with a letter
&& token.indexOf( '.' ) < 0; // don't qualify already-qualified identifiers
}

private static boolean isBoolean(String lcToken) {
Expand Down
Loading

AltStyle によって変換されたページ (->オリジナル) /