Skip to content

Commit 614c780

Browse files
committed
[hibernate#1984] Test queries on JSON fields
1 parent 7a34897 commit 614c780

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.types;
7+
8+
import java.math.BigDecimal;
9+
import java.util.Collection;
10+
import java.util.List;
11+
import java.util.Objects;
12+
13+
import org.hibernate.annotations.JdbcType;
14+
import org.hibernate.annotations.JdbcTypeCode;
15+
import org.hibernate.dialect.PostgreSQLJsonPGObjectJsonbType;
16+
import org.hibernate.reactive.BaseReactiveTest;
17+
import org.hibernate.reactive.annotations.EnabledFor;
18+
import org.hibernate.type.SqlTypes;
19+
20+
import org.junit.jupiter.api.BeforeEach;
21+
import org.junit.jupiter.api.Test;
22+
23+
import io.vertx.core.json.JsonObject;
24+
import io.vertx.junit5.Timeout;
25+
import io.vertx.junit5.VertxTestContext;
26+
import jakarta.persistence.Column;
27+
import jakarta.persistence.Embeddable;
28+
import jakarta.persistence.Entity;
29+
import jakarta.persistence.Id;
30+
import jakarta.persistence.Table;
31+
import jakarta.persistence.criteria.CriteriaBuilder;
32+
import jakarta.persistence.criteria.CriteriaQuery;
33+
import jakarta.persistence.criteria.Root;
34+
35+
import static java.util.concurrent.TimeUnit.MINUTES;
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.COCKROACHDB;
38+
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL;
39+
40+
/**
41+
* Test queries on JSON fields on PostgreSQL
42+
* <p>
43+
* There is no portable way to test queries on JSON fields with JPQL.
44+
* So, we test this with PostgreSQL using native syntax.
45+
* </p>
46+
*/
47+
@Timeout(value = 10, timeUnit = MINUTES)
48+
@EnabledFor(POSTGRESQL)
49+
@EnabledFor(COCKROACHDB)
50+
public class JsonQueryTest extends BaseReactiveTest {
51+
52+
private final static BigDecimal PIE = BigDecimal.valueOf( 3.1416 );
53+
private final static BigDecimal TAO = BigDecimal.valueOf( 6.2832 );
54+
55+
final Book fakeHistory = new Book( 3, "Fake History", new JsonObject().put( "amount", PIE ), new Book.Author( "Jo", "Hedwig Teeuwisse" ) );
56+
final Book theBookOfM = new Book( 5, "The Book of M", new JsonObject().put( "amount", TAO ), new Book.Author( "Peng", "Shepherd" ) );
57+
58+
@Override
59+
protected Collection<Class<?>> annotatedEntities() {
60+
return List.of( Book.class );
61+
}
62+
63+
@BeforeEach
64+
public void populateDb(VertxTestContext context) {
65+
test( context, getMutinySessionFactory().withTransaction( s -> s.persistAll( theBookOfM, fakeHistory ) ) );
66+
}
67+
68+
@Test
69+
public void criteiraSelectAll(VertxTestContext context) {
70+
CriteriaBuilder cb = getMutinySessionFactory().getCriteriaBuilder();
71+
CriteriaQuery<Book> bookQuery = cb.createQuery( Book.class );
72+
bookQuery.from( Book.class );
73+
test( context, getMutinySessionFactory()
74+
.withTransaction( s -> s.createQuery( bookQuery ).getResultList() )
75+
.invoke( results -> assertThat( results ).containsExactlyInAnyOrder( fakeHistory, theBookOfM ) )
76+
);
77+
}
78+
79+
@Test
80+
public void nativeSelectAll(VertxTestContext context) {
81+
test( context, getMutinySessionFactory()
82+
.withTransaction( s -> s.createNativeQuery( "select * from BookWithJson", Book.class ).getResultList() )
83+
.invoke( results -> assertThat( results ).containsExactlyInAnyOrder( fakeHistory, theBookOfM ) )
84+
);
85+
}
86+
87+
@Test
88+
public void criteriaQueryWithJsonb(VertxTestContext context) {
89+
CriteriaBuilder cb = getMutinySessionFactory().getCriteriaBuilder();
90+
CriteriaQuery<Book> bookQuery = cb.createQuery( Book.class );
91+
Root<Book> bookRoot = bookQuery.from( Book.class );
92+
bookQuery.where( cb.equal(
93+
cb.function( "jsonb_extract_path_text", String.class, bookRoot.get( "author" ), cb.literal( "name" ) ),
94+
cb.literal( fakeHistory.author.name )
95+
) );
96+
97+
test( context, getMutinySessionFactory()
98+
.withTransaction( s -> s.createQuery( bookQuery ).getSingleResult() )
99+
.invoke( result -> assertThat( result ).isEqualTo( fakeHistory ) )
100+
);
101+
}
102+
103+
@Test
104+
public void criteriaQueryWithJson(VertxTestContext context) {
105+
CriteriaBuilder cb = getMutinySessionFactory().getCriteriaBuilder();
106+
CriteriaQuery<Book> bookQuery = cb.createQuery( Book.class );
107+
bookQuery.from( Book.class );
108+
bookQuery.where( cb.between(
109+
cb.function( "sql", BigDecimal.class, cb.literal( "(price ->> ?)::decimal" ), cb.literal( "amount" ) ),
110+
BigDecimal.valueOf( 4.0 ),
111+
BigDecimal.valueOf( 100.0 )
112+
) );
113+
114+
test( context, getMutinySessionFactory()
115+
.withTransaction( s -> s.createQuery( bookQuery ).getSingleResult() )
116+
.invoke( result -> assertThat( result ).isEqualTo( theBookOfM ) )
117+
);
118+
}
119+
120+
@Test
121+
public void nativeQueryWithJson(VertxTestContext context) {
122+
test( context, getMutinySessionFactory()
123+
.withTransaction( s -> s
124+
.createNativeQuery(
125+
"select * from BookWithJson b where (b.price ->> 'amount')::decimal between ?1 and ?2",
126+
Book.class
127+
)
128+
.setParameter( 1, BigDecimal.valueOf( 4.0 ) )
129+
.setParameter( 2, BigDecimal.valueOf( 100.0 ) )
130+
.getSingleResult()
131+
)
132+
.invoke( result -> assertThat( result ).isEqualTo( theBookOfM ) )
133+
);
134+
}
135+
136+
@Test
137+
public void hqlQueryWithJson(VertxTestContext context) {
138+
test( context, getMutinySessionFactory()
139+
.withTransaction( s -> s
140+
.createSelectionQuery(
141+
"from Book where sql('(price ->> ?)::decimal', 'amount') between ?1 and ?2",
142+
Book.class
143+
)
144+
.setParameter( 1, BigDecimal.valueOf( 4.0 ) )
145+
.setParameter( 2, BigDecimal.valueOf( 100.0 ) )
146+
.getSingleResult()
147+
)
148+
.invoke( result -> assertThat( result ).isEqualTo( theBookOfM ) )
149+
);
150+
}
151+
152+
@Entity(name = "Book")
153+
@Table(name = "BookWithJson")
154+
public static class Book {
155+
156+
@Id
157+
Integer id;
158+
159+
String title;
160+
161+
@Column(name = "price")
162+
JsonObject price;
163+
164+
@JdbcTypeCode(SqlTypes.JSON)
165+
@JdbcType(PostgreSQLJsonPGObjectJsonbType.class)
166+
Author author;
167+
168+
@Embeddable
169+
public static class Author {
170+
private String name;
171+
private String surname;
172+
173+
public Author() {
174+
}
175+
176+
public Author(String name, String surname) {
177+
this.name = name;
178+
this.surname = surname;
179+
}
180+
181+
public String getName() {
182+
return name;
183+
}
184+
185+
public void setName(String name) {
186+
this.name = name;
187+
}
188+
189+
public String getSurname() {
190+
return surname;
191+
}
192+
193+
public void setSurname(String surname) {
194+
this.surname = surname;
195+
}
196+
197+
@Override
198+
public boolean equals(Object o) {
199+
if ( this == o ) {
200+
return true;
201+
}
202+
if ( o == null || getClass() != o.getClass() ) {
203+
return false;
204+
}
205+
Author author = (Author) o;
206+
return Objects.equals( name, author.name ) && Objects.equals( surname, author.surname );
207+
}
208+
209+
@Override
210+
public int hashCode() {
211+
return Objects.hash( name, surname );
212+
}
213+
214+
@Override
215+
public String toString() {
216+
return name + ' ' + surname;
217+
}
218+
}
219+
220+
public Book() {
221+
}
222+
223+
public Book(Integer id, String title, JsonObject price, Author author) {
224+
this.id = id;
225+
this.title = title;
226+
this.price = price;
227+
this.author = author;
228+
}
229+
230+
@Override
231+
public boolean equals(Object o) {
232+
if ( this == o ) {
233+
return true;
234+
}
235+
if ( o == null || getClass() != o.getClass() ) {
236+
return false;
237+
}
238+
Book book = (Book) o;
239+
return Objects.equals( id, book.id ) && Objects.equals(
240+
title,
241+
book.title
242+
) && Objects.equals( price, book.price ) && Objects.equals( author, book.author );
243+
}
244+
245+
@Override
246+
public int hashCode() {
247+
return Objects.hash( id, title, price, author );
248+
}
249+
250+
@Override
251+
public String toString() {
252+
return id + ":" + title + ":" + price + ":" + author;
253+
}
254+
}
255+
}

0 commit comments

Comments
 (0)