Skip to content

Commit 9cf101f

Browse files
author
Martin Fekete
committed
@JdbcTypeCode support
1 parent 6bee6e9 commit 9cf101f

File tree

4 files changed

+112
-4
lines changed

4 files changed

+112
-4
lines changed

rsql-jpa/src/main/java/io/github/perplexhub/rsql/jsonb/JsonbSupport.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import static io.github.perplexhub.rsql.RSQLVisitorBase.getEntityManagerMap;
55

6+
import java.lang.annotation.Annotation;
67
import java.lang.reflect.Field;
78
import java.util.EnumSet;
89
import java.util.Map;
@@ -19,13 +20,22 @@
1920
import jakarta.persistence.criteria.Path;
2021
import jakarta.persistence.metamodel.Attribute;
2122
import jakarta.persistence.metamodel.ManagedType;
23+
import org.hibernate.annotations.JdbcTypeCode;
24+
import org.hibernate.type.SqlTypes;
2225
import org.springframework.orm.jpa.vendor.Database;
26+
import org.springframework.util.ClassUtils;
2327

2428
/**
2529
* Support for jsonb expression.
2630
*/
2731
public class JsonbSupport {
2832

33+
/**
34+
* is annotation present on classpath ?
35+
*/
36+
private static final boolean isHibernatePresent = ClassUtils.isPresent(
37+
"org.hibernate.annotations.JdbcTypeCode", JsonbSupport.class.getClassLoader());
38+
2939
private static final Set<Database> JSON_SUPPORT = EnumSet.of(Database.POSTGRESQL);
3040

3141
private static final Map<ComparisonOperator, ComparisonOperator> NEGATE_OPERATORS =
@@ -99,15 +109,43 @@ public static boolean isJsonType(String mappedProperty, ManagedType<?> classMeta
99109
* @return true if the attribute is a jsonb attribute
100110
*/
101111
private static boolean isJsonColumn(Attribute<?, ?> attribute) {
112+
return isJsonbColumn(attribute) || isJdbcTypeCodeJSON(attribute);
113+
}
114+
115+
/**
116+
* Returns whether the given attribute is a jsonb column.
117+
*
118+
* @param attribute the attribute
119+
* @return true if the column is a jsonb column
120+
*/
121+
private static boolean isJsonbColumn(Attribute<?, ?> attribute) {
122+
return getFieldAnnotation(attribute, Column.class)
123+
.map(Column::columnDefinition)
124+
.map(s -> s.toLowerCase().startsWith("jsonb"))
125+
.orElse(false);
126+
}
127+
128+
/**
129+
* Returns whether the given attribute is annotated with {@link JdbcTypeCode} and {@code value == SqlTypes.JSON}.
130+
*
131+
* @param attribute the attribute
132+
* @return true if the column is a jsonb column
133+
*/
134+
private static boolean isJdbcTypeCodeJSON(Attribute<?, ?> attribute) {
135+
return isHibernatePresent && getFieldAnnotation(attribute, JdbcTypeCode.class)
136+
.map(JdbcTypeCode::value)
137+
.map(code -> SqlTypes.JSON == code)
138+
.orElse(false);
139+
}
140+
141+
private static <T extends Annotation> Optional<T> getFieldAnnotation(Attribute<?, ?> attribute, Class<T> annotationClass) {
102142
return Optional.ofNullable(attribute)
103143
.filter(attr -> attr.getJavaMember() instanceof Field)
104144
.map(attr -> ((Field) attr.getJavaMember()))
105-
.map(field -> field.getAnnotation(Column.class))
106-
.map(Column::columnDefinition)
107-
.map("jsonb"::equalsIgnoreCase)
108-
.orElse(false);
145+
.map(field -> field.getAnnotation(annotationClass));
109146
}
110147

148+
111149
/**
112150
* Returns the database of the given attribute.
113151
*

rsql-jpa/src/test/java/io/github/perplexhub/rsql/RSQLJPASupportPostgresJsonTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package io.github.perplexhub.rsql;
22

33
import io.github.perplexhub.rsql.jsonb.JsonbConfiguration;
4+
import io.github.perplexhub.rsql.model.AnotherJsonbEntity;
45
import io.github.perplexhub.rsql.model.EntityWithJsonb;
56
import io.github.perplexhub.rsql.model.JsonbEntity;
67
import io.github.perplexhub.rsql.model.PostgresJsonEntity;
8+
import io.github.perplexhub.rsql.repository.jpa.postgres.AnotherJsonbEntityRepository;
79
import io.github.perplexhub.rsql.repository.jpa.postgres.EntityWithJsonbRepository;
810
import io.github.perplexhub.rsql.repository.jpa.postgres.JsonbEntityRepository;
911
import io.github.perplexhub.rsql.repository.jpa.postgres.PostgresJsonEntityRepository;
1012
import jakarta.persistence.EntityManager;
1113
import org.junit.jupiter.api.AfterEach;
1214
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.Test;
1316
import org.junit.jupiter.params.ParameterizedTest;
1417
import org.junit.jupiter.params.provider.Arguments;
1518
import org.junit.jupiter.params.provider.MethodSource;
@@ -44,6 +47,9 @@ class RSQLJPASupportPostgresJsonTest {
4447
@Autowired
4548
private JsonbEntityRepository jsonbEntityRepository;
4649

50+
@Autowired
51+
private AnotherJsonbEntityRepository anotherJsonbEntityRepository;
52+
4753
@BeforeEach
4854
void setup(@Autowired EntityManager em) {
4955
RSQLVisitorBase.setEntityManagerDatabase(Map.of(em, Database.POSTGRESQL));
@@ -737,4 +743,16 @@ void testJsonSearchCustomFunction(List<PostgresJsonEntity> entities, String rsql
737743

738744
entities.forEach(e -> e.setId(null));
739745
}
746+
747+
@Test
748+
void testAlternateJsonColumnDefinitions() {
749+
anotherJsonbEntityRepository.saveAllAndFlush(List.of(
750+
AnotherJsonbEntity.builder().id(UUID.randomUUID()).data("{\"a\":\"b\",\"c\":1}").other("{\"d\":\"e\"}").build(),
751+
AnotherJsonbEntity.builder().id(UUID.randomUUID()).data("{\"a\":\"q\",\"c\":2}").other("{\"d\":\"h\"}").build()
752+
));
753+
assertThat(anotherJsonbEntityRepository.findAll(toSpecification("data.a==b"))).hasSize(1);
754+
assertThat(anotherJsonbEntityRepository.findAll(toSpecification("other.d==h"))).hasSize(1);
755+
assertThat(anotherJsonbEntityRepository.findAll(toSpecification("generated.a==b"))).hasSize(1);
756+
assertThat(anotherJsonbEntityRepository.findAll(toSpecification("formula.c==1"))).hasSize(1);;
757+
}
740758
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.github.perplexhub.rsql.model;
2+
3+
import io.hypersistence.utils.hibernate.type.json.JsonType;
4+
import jakarta.persistence.Column;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.Id;
7+
import lombok.*;
8+
import org.hibernate.annotations.Formula;
9+
import org.hibernate.annotations.JdbcTypeCode;
10+
import org.hibernate.annotations.Type;
11+
import org.hibernate.type.SqlTypes;
12+
13+
import java.util.UUID;
14+
15+
@Getter
16+
@Setter
17+
@EqualsAndHashCode(of = "id")
18+
@ToString
19+
@Entity
20+
@NoArgsConstructor
21+
@Builder
22+
@AllArgsConstructor
23+
public class AnotherJsonbEntity {
24+
25+
@Id
26+
private UUID id;
27+
28+
@JdbcTypeCode(SqlTypes.JSON)
29+
private String data;
30+
31+
@Type(JsonType.class)
32+
@Column(columnDefinition = "jsonb")
33+
private String other;
34+
35+
@Column(columnDefinition = "jsonb generated always as (data) stored", insertable = false, updatable = false)
36+
private String generated;
37+
38+
@JdbcTypeCode(SqlTypes.JSON)
39+
@Formula("jsonb_set(data, '{f}','\"r\"', true)")
40+
private String formula;
41+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.perplexhub.rsql.repository.jpa.postgres;
2+
3+
import io.github.perplexhub.rsql.model.AnotherJsonbEntity;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
6+
7+
import java.util.UUID;
8+
9+
public interface AnotherJsonbEntityRepository extends JpaRepository<AnotherJsonbEntity, UUID>,
10+
JpaSpecificationExecutor<AnotherJsonbEntity> {
11+
}

0 commit comments

Comments
 (0)