Skip to content

Commit 0cae5e1

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

File tree

2 files changed

+206
-3
lines changed

2 files changed

+206
-3
lines changed

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

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

0 commit comments

Comments
 (0)