Skip to content

Commit 39b8da6

Browse files
author
Mike Mannion
committed
HHH-19704 Added dialect enhancement for check if dialect supports listAgg(). Minor refactoring of orderedSet/listagg logic.
1 parent 00f0af4 commit 39b8da6

File tree

3 files changed

+90
-23
lines changed

3 files changed

+90
-23
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3676,6 +3676,19 @@ public Set<String> getKeywords() {
36763676
return sqlKeywords;
36773677
}
36783678

3679+
/**
3680+
* Whether this Dialect supports the LISTAGG ordered-set aggregate (including its extensions).
3681+
* <p>
3682+
* This is used by {@code org.hibernate.sql.Template} to recognize LISTAGG extension soft keywords
3683+
* like OVERFLOW/TRUNCATE/etc. Default is {@code true} to preserve historical behavior of Template
3684+
* scanning regardless of dialect.
3685+
*
3686+
* @since 7.1
3687+
*/
3688+
public boolean supportsListagg() {
3689+
return true;
3690+
}
3691+
36793692
/**
36803693
* The {@link IdentifierHelper} indicated by this dialect for handling identifier conversions.
36813694
* Returning {@code null} is allowed and indicates that Hibernate should fall back to building

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

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -247,18 +247,6 @@ else if ( quotedIdentifier && dialect.closeQuote()==token.charAt(0) ) {
247247
final boolean isQuoted =
248248
quoted || quotedIdentifier || isQuoteCharacter;
249249

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-
262250
if ( isQuoted || isWhitespace ) {
263251
processedToken = token;
264252
}
@@ -334,17 +322,6 @@ else if ( isCurrent( lcToken, nextToken, sql, symbols, tokens ) ) {
334322
else if ( isBoolean( lcToken ) ) {
335323
processedToken = dialect.toBooleanValueString( parseBoolean( token ) );
336324
}
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-
}
348325
else if ( isFunctionCall( nextToken, sql, symbols, tokens ) ) {
349326
if ( FUNCTION_WITH_FROM_KEYWORDS.contains( lcToken ) ) {
350327
inExtractOrTrim = true;
@@ -359,9 +336,24 @@ else if ( isFunctionCall( nextToken, sql, symbols, tokens ) ) {
359336
}
360337
processedToken = token;
361338
}
339+
else if ( afterOrderedSetArgs && (inListaggExtension
340+
? ( LISTAGG_EXTENSION_KEYWORDS.contains( lcToken ) && dialect.supportsListagg() )
341+
: "within".equals( lcToken )) ) {
342+
if ( "group".equals( lcToken ) ) {
343+
// end special handling after GROUP (inclusive)
344+
afterOrderedSetArgs = false;
345+
inListaggExtension = false;
346+
}
347+
processedToken = token;
348+
}
362349
else if ( isAliasableIdentifier( token, lcToken, nextToken,
363350
sql, symbols, tokens, wasAfterCurrent,
364351
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;
365357
processedToken = alias + '.' + dialect.quote(token);
366358
}
367359
else {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.sql;
6+
7+
import org.hibernate.dialect.Dialect;
8+
import org.hibernate.sql.Template;
9+
import org.hibernate.type.spi.TypeConfiguration;
10+
import org.junit.jupiter.api.Test;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
14+
/**
15+
* Tests that LISTAGG extension soft keywords are only treated specially
16+
* when the Dialect reports support for LISTAGG.
17+
*/
18+
public class TemplateListaggDialectSupportTest {
19+
20+
private static final TypeConfiguration TYPE_CONFIGURATION = new TypeConfiguration();
21+
22+
static class NoListaggDialect extends Dialect {
23+
@Override
24+
public boolean supportsListagg() {
25+
return false;
26+
}
27+
}
28+
29+
static class SupportsListaggDialect extends Dialect {
30+
@Override
31+
public boolean supportsListagg() {
32+
return true;
33+
}
34+
}
35+
36+
@Test
37+
public void listaggExtensionsAllowedWhenSupported() {
38+
final Dialect dialect = new SupportsListaggDialect();
39+
final String sql = "SELECT LISTAGG(employee_name, ', ') ON OVERFLOW TRUNCATE WITH COUNT WITHIN GROUP (ORDER BY hire_date) FROM employees";
40+
final String rendered = Template.renderWhereStringTemplate(sql, dialect, TYPE_CONFIGURATION);
41+
final String expected = "SELECT LISTAGG({@}.employee_name, ', ') ON OVERFLOW TRUNCATE WITH COUNT WITHIN GROUP (ORDER BY {@}.hire_date) FROM employees";
42+
assertEquals(expected, rendered);
43+
}
44+
45+
@Test
46+
public void listaggExtensionsDisallowedWhenNotSupported() {
47+
final Dialect dialect = new NoListaggDialect();
48+
final String sql = "SELECT LISTAGG(employee_name, ', ') ON OVERFLOW TRUNCATE WITH COUNT WITHIN GROUP (ORDER BY hire_date) FROM employees";
49+
final String rendered = Template.renderWhereStringTemplate(sql, dialect, TYPE_CONFIGURATION);
50+
final String expected = "SELECT LISTAGG({@}.employee_name, ', ') ON {@}.OVERFLOW {@}.TRUNCATE WITH {@}.COUNT WITHIN GROUP (ORDER BY {@}.hire_date) FROM employees";
51+
assertEquals(expected, rendered);
52+
}
53+
54+
@Test
55+
public void listaggErrorExtensionDisallowedWhenNotSupported() {
56+
final Dialect dialect = new NoListaggDialect();
57+
final String sql = "SELECT LISTAGG(employee_name, ', ') ON OVERFLOW ERROR WITHIN GROUP (ORDER BY hire_date) FROM employees";
58+
final String rendered = Template.renderWhereStringTemplate(sql, dialect, TYPE_CONFIGURATION);
59+
final String expected = "SELECT LISTAGG({@}.employee_name, ', ') ON {@}.OVERFLOW {@}.ERROR WITHIN GROUP (ORDER BY {@}.hire_date) FROM employees";
60+
assertEquals(expected, rendered);
61+
}
62+
}

0 commit comments

Comments
 (0)