Skip to content

Commit e38ae55

Browse files
peter1123581321beikov
authored andcommitted
HHH-18898 fix of a NPE when using an embeddable with a specified JavaType
1 parent 1c1658d commit e38ae55

File tree

2 files changed

+206
-6
lines changed

2 files changed

+206
-6
lines changed

hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7638,9 +7638,11 @@ public void visitInListPredicate(InListPredicate inListPredicate) {
76387638
if ( ( lhsTuple = getSqlTuple( inListPredicate.getTestExpression() ) ) != null ) {
76397639
if ( lhsTuple.getExpressions().size() == 1 ) {
76407640
// Special case for tuples with arity 1 as any DBMS supports scalar IN predicates
7641-
itemAccessor = listExpression ->
7642-
getSqlTuple( listExpression )
7643-
.getExpressions().get( 0 );
7641+
if ( getSqlTuple( listExpressions.get( 0 ) ) != null ) {
7642+
itemAccessor = listExpression ->
7643+
getSqlTuple( listExpression )
7644+
.getExpressions().get( 0 );
7645+
}
76447646
}
76457647
else if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) {
76467648
final ComparisonOperator comparisonOperator = inListPredicate.isNegated() ?
@@ -8263,12 +8265,12 @@ else if ( rhsExpression instanceof Any any ) {
82638265
final ComparisonOperator operator = comparisonPredicate.getOperator();
82648266
if ( lhsTuple.getExpressions().size() == 1 ) {
82658267
// Special case for tuples with arity 1 as any DBMS supports scalar IN predicates
8266-
if ( subquery == null ) {
8268+
if ( subquery == null && (rhsTuple = getSqlTuple(
8269+
comparisonPredicate.getRightHandExpression() )) != null ) {
82678270
renderComparison(
82688271
lhsTuple.getExpressions().get( 0 ),
82698272
operator,
8270-
getSqlTuple( comparisonPredicate.getRightHandExpression() )
8271-
.getExpressions().get( 0 )
8273+
rhsTuple.getExpressions().get( 0 )
82728274
);
82738275
}
82748276
else {
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.embeddable;
6+
7+
import jakarta.persistence.*;
8+
import org.hibernate.annotations.JavaType;
9+
import org.hibernate.query.spi.QueryImplementor;
10+
import org.hibernate.testing.orm.junit.*;
11+
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
12+
import org.hibernate.type.BasicType;
13+
import org.hibernate.type.SqlTypes;
14+
import org.hibernate.type.descriptor.WrapperOptions;
15+
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
16+
import org.hibernate.type.descriptor.java.LocalDateJavaType;
17+
import org.hibernate.type.descriptor.jdbc.DateJdbcType;
18+
import org.hibernate.type.descriptor.jdbc.JdbcType;
19+
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.ValueSource;
22+
23+
import java.time.LocalDate;
24+
import java.util.List;
25+
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertFalse;
28+
29+
@JiraKey("HHH-18898")
30+
@DomainModel(
31+
annotatedClasses = {
32+
EmbeddableWithJavaTypeTest.EntityEmbedCustom.class,
33+
EmbeddableWithJavaTypeTest.EntityEmbedNative.class
34+
}
35+
)
36+
@SessionFactory
37+
class EmbeddableWithJavaTypeTest implements SessionFactoryScopeAware {
38+
39+
private SessionFactoryScope scope;
40+
41+
@Override
42+
public void injectSessionFactoryScope(SessionFactoryScope scope) {
43+
this.scope = scope;
44+
}
45+
46+
// uses an embeddable with a custom java type
47+
@ParameterizedTest
48+
@ValueSource(strings = {
49+
"select z from EntityEmbedCustom z where embedCustom.value=:datum",
50+
"select z from EntityEmbedCustom z where :datum=embedCustom.value",
51+
"select z from EntityEmbedCustom z where embedCustom=:datum", // this query failed with the bug
52+
"select z from EntityEmbedCustom z where :datum=embedCustom",
53+
"select z from EntityEmbedCustom z where embedCustom.value in (:datum)",
54+
"select z from EntityEmbedCustom z where embedCustom in (:datum)" // failed as well
55+
})
56+
void hhh18898Test_embedCustom(String hql) {
57+
58+
// prepare
59+
scope.inTransaction( session -> {
60+
EntityEmbedCustom e = new EntityEmbedCustom();
61+
e.id = 1;
62+
EmbedCustom datum = new EmbedCustom();
63+
datum.value = new MyDate( LocalDate.now() );
64+
e.embedCustom = datum;
65+
session.persist( e );
66+
} );
67+
68+
// assert
69+
scope.inTransaction( session -> {
70+
QueryImplementor<EntityEmbedCustom> query = session.createQuery( hql, EntityEmbedCustom.class );
71+
query.setParameter( "datum", new MyDate( LocalDate.now() ), MyDateJavaType.TYPE );
72+
List<EntityEmbedCustom> resultList = query.getResultList();
73+
assertFalse( resultList.isEmpty() );
74+
assertEquals( LocalDate.now(), resultList.get( 0 ).embedCustom.value.wrapped );
75+
session.remove( resultList.get( 0 ) );
76+
} );
77+
}
78+
79+
// uses an embeddable with a native java type
80+
@ParameterizedTest
81+
@ValueSource(strings = {
82+
"select z from EntityEmbedNative z where embedNative.value=:datum",
83+
"select z from EntityEmbedNative z where :datum=embedNative.value",
84+
"select z from EntityEmbedNative z where embedNative=:datum", // this query failed with the bug
85+
"select z from EntityEmbedNative z where :datum=embedNative",
86+
"select z from EntityEmbedNative z where embedNative.value in (:datum)",
87+
"select z from EntityEmbedNative z where embedNative in (:datum)" // failed as well
88+
})
89+
void hhh18898Test_embedSingle(String hql) {
90+
91+
// prepare
92+
scope.inTransaction( session -> {
93+
EntityEmbedNative e = new EntityEmbedNative();
94+
e.id = 1;
95+
EmbedNative datum = new EmbedNative();
96+
datum.value = LocalDate.now();
97+
e.embedNative = datum;
98+
session.persist( e );
99+
} );
100+
101+
// assert
102+
scope.inTransaction( session -> {
103+
QueryImplementor<EntityEmbedNative> query = session.createQuery( hql, EntityEmbedNative.class );
104+
query.setParameter( "datum", LocalDate.now(), LocalDateJavaType.INSTANCE.getJavaType() );
105+
List<EntityEmbedNative> resultList = query.getResultList();
106+
assertFalse( resultList.isEmpty() );
107+
assertEquals( LocalDate.now(), resultList.get( 0 ).embedNative.value );
108+
session.remove( resultList.get( 0 ) );
109+
} );
110+
}
111+
112+
@Embeddable
113+
public static class EmbedCustom {
114+
115+
@Column(name = "DATUM")
116+
@JavaType(MyDateJavaType.class)
117+
MyDate value;
118+
119+
}
120+
121+
@Entity(name = "EntityEmbedCustom")
122+
public static class EntityEmbedCustom {
123+
124+
@Id
125+
@Column(name = "id")
126+
long id;
127+
128+
@Embedded
129+
EmbedCustom embedCustom;
130+
}
131+
132+
@Embeddable
133+
public static class EmbedNative {
134+
135+
@Column(name = "DATUM")
136+
@JavaType(LocalDateJavaType.class)
137+
LocalDate value;
138+
}
139+
140+
@Entity(name = "EntityEmbedNative")
141+
public static class EntityEmbedNative {
142+
143+
@Id
144+
@Column(name = "id")
145+
long id;
146+
147+
@Embedded
148+
EmbedNative embedNative;
149+
}
150+
151+
public static class MyDate {
152+
private final LocalDate wrapped;
153+
154+
public MyDate(LocalDate dateValue) {
155+
wrapped = dateValue;
156+
}
157+
158+
public LocalDate toLocalDate() {
159+
return wrapped;
160+
}
161+
}
162+
163+
public static class MyDateJavaType extends AbstractClassJavaType<MyDate> {
164+
private static final MyDateJavaType INSTANCE = new MyDateJavaType();
165+
public static final BasicType<MyDate> TYPE = new AbstractSingleColumnStandardBasicType<>( DateJdbcType.INSTANCE,
166+
INSTANCE ) {
167+
168+
@Override
169+
public String getName() {
170+
return "MyDateJavaType";
171+
}
172+
};
173+
174+
protected MyDateJavaType() {
175+
super( MyDate.class );
176+
}
177+
178+
@Override
179+
public <X> X unwrap(final MyDate value, final Class<X> type, final WrapperOptions options) {
180+
LocalDate dateValue = (value == null ? null : value.toLocalDate());
181+
return LocalDateJavaType.INSTANCE.unwrap( dateValue, type, options );
182+
}
183+
184+
@Override
185+
public <X> MyDate wrap(final X value, final WrapperOptions options) {
186+
if ( value instanceof MyDate ) {
187+
return (MyDate) value;
188+
}
189+
LocalDate dateValue = LocalDateJavaType.INSTANCE.wrap( value, options );
190+
return dateValue == null ? null : new MyDate( dateValue );
191+
}
192+
193+
@Override
194+
public JdbcType getRecommendedJdbcType(final JdbcTypeIndicators context) {
195+
return context.getJdbcType( SqlTypes.DATE );
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)