Skip to content

Commit 6ea6a64

Browse files
committed
[HHH-16861] HQL ordinal() function
`ordinal(field)` is equivalent to `cast(enum as Integer`, implementation taken from CastStrEmulation Added new rule in both Lexer and parser (remember to add nakedidentifier)
1 parent 0e5846b commit 6ea6a64

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed

hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ ON : [oO] [nN];
273273
ONLY : [oO] [nN] [lL] [yY];
274274
OR : [oO] [rR];
275275
ORDER : [oO] [rR] [dD] [eE] [rR];
276+
ORDINAL : [oO] [rR] [dD] [iI] [nN] [aA] [lL];
276277
OTHERS : [oO] [tT] [hH] [eE] [rR] [sS];
277278
OUTER : [oO] [uU] [tT] [eE] [rR];
278279
OVER : [oO] [vV] [eE] [rR];

hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,7 @@ jsonUniqueKeysClause
18631863
| ONLY
18641864
| OR
18651865
| ORDER
1866+
| ORDINAL
18661867
| OTHERS
18671868
// | OUTER
18681869
| OVER

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.hibernate.dialect.function.LpadRpadPadEmulation;
6969
import org.hibernate.dialect.function.SqlFunction;
7070
import org.hibernate.dialect.function.TrimFunction;
71+
import org.hibernate.dialect.function.OrdinalFunction;
7172
import org.hibernate.dialect.identity.IdentityColumnSupport;
7273
import org.hibernate.dialect.identity.IdentityColumnSupportImpl;
7374
import org.hibernate.dialect.lock.LockingStrategy;
@@ -1226,6 +1227,11 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
12261227
functionContributions.getFunctionRegistry().register( "str",
12271228
new CastStrEmulation( typeConfiguration ) );
12281229

1230+
// Function to convert enum mapped as Ordinal to their ordinal value
1231+
1232+
functionContributions.getFunctionRegistry().register( "ordinal",
1233+
new OrdinalFunction( typeConfiguration ) );
1234+
12291235
//format() function for datetimes, emulated on many databases using the
12301236
//Oracle-style to_char() function, and on others using their native
12311237
//formatting functions
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect.function;
6+
7+
import org.hibernate.query.ReturnableType;
8+
import org.hibernate.query.spi.QueryEngine;
9+
import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor;
10+
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
11+
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
12+
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
13+
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
14+
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
15+
import org.hibernate.query.sqm.tree.SqmTypedNode;
16+
import org.hibernate.query.sqm.tree.expression.SqmCastTarget;
17+
import org.hibernate.type.StandardBasicTypes;
18+
import org.hibernate.type.spi.TypeConfiguration;
19+
20+
import java.util.List;
21+
22+
import static java.util.Arrays.asList;
23+
24+
/**
25+
* The HQL {@code ordinal()} function is now considered a synonym for {@code cast(x as Integer)}.
26+
* Same as {@link CastStrEmulation} but for Integer.
27+
* This is useful for enum types mapped as ORDINAL.
28+
*
29+
* @author Luca Molteni
30+
*/
31+
public class OrdinalFunction
32+
extends AbstractSqmFunctionDescriptor {
33+
34+
public OrdinalFunction(TypeConfiguration typeConfiguration) {
35+
super(
36+
"ordinal",
37+
StandardArgumentsValidators.exactly( 1 ),
38+
StandardFunctionReturnTypeResolvers.invariant(
39+
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER )
40+
),
41+
null
42+
);
43+
}
44+
45+
protected OrdinalFunction(
46+
String name,
47+
ArgumentsValidator argumentsValidator,
48+
FunctionReturnTypeResolver returnTypeResolver) {
49+
super( name, argumentsValidator, returnTypeResolver, null );
50+
}
51+
52+
@Override
53+
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
54+
List<? extends SqmTypedNode<?>> arguments,
55+
ReturnableType<T> impliedResultType,
56+
QueryEngine queryEngine) {
57+
final SqmTypedNode<?> argument = arguments.get( 0 );
58+
return queryEngine.getSqmFunctionRegistry().findFunctionDescriptor( "cast" )
59+
.generateSqmExpression(
60+
asList(
61+
argument,
62+
new SqmCastTarget<>(
63+
queryEngine.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER ),
64+
queryEngine.getCriteriaBuilder()
65+
)
66+
),
67+
impliedResultType,
68+
queryEngine
69+
);
70+
}
71+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.hql;
6+
7+
import org.hibernate.testing.orm.domain.gambit.Shirt;
8+
import org.hibernate.testing.orm.junit.DomainModel;
9+
import org.hibernate.testing.orm.junit.Jira;
10+
import org.hibernate.testing.orm.junit.SessionFactory;
11+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
12+
import org.junit.jupiter.api.BeforeAll;
13+
import org.junit.jupiter.api.Test;
14+
15+
import java.util.List;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
@DomainModel(annotatedClasses = {
20+
Shirt.class,
21+
Shirt.Size.class})
22+
@SessionFactory
23+
@Jira("https://hibernate.atlassian.net/browse/HHH-16861")
24+
public class EnumTest {
25+
26+
@BeforeAll
27+
public void setUp(SessionFactoryScope scope) {
28+
scope.inTransaction(session -> {
29+
Shirt largeShirt = new Shirt();
30+
largeShirt.setId(10_000_000);
31+
largeShirt.setSize(Shirt.Size.LARGE); // Ordinal 2
32+
33+
Shirt smallShirt = new Shirt();
34+
smallShirt.setId(10_000_001);
35+
smallShirt.setSize(Shirt.Size.SMALL); // Ordinal 0
36+
37+
session.persist(largeShirt);
38+
session.persist(smallShirt);
39+
});
40+
}
41+
42+
@Test
43+
public void testOrdinalFunctionOnEnum(SessionFactoryScope scope) {
44+
scope.inTransaction(session -> {
45+
List<Integer> largeShirtsOrdinalFunction = session.createQuery(
46+
"select ordinal(size)" +
47+
"from Shirt s " +
48+
"where s.size = :size",
49+
Integer.class)
50+
.setParameter("size", Shirt.Size.LARGE)
51+
.getResultList();
52+
53+
List<Integer> largeShirtsOrdinal = session.createQuery(
54+
"select cast(s.size as Integer)" +
55+
"from Shirt s " +
56+
"where s.size = :size",
57+
Integer.class)
58+
.setParameter("size", Shirt.Size.LARGE)
59+
.getResultList();
60+
61+
assertThat(largeShirtsOrdinal).hasSize(1);
62+
assertThat(largeShirtsOrdinal).hasSameElementsAs(largeShirtsOrdinalFunction);
63+
});
64+
65+
}
66+
67+
}

0 commit comments

Comments
 (0)