Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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