diff --git a/rsql-common/pom.xml b/rsql-common/pom.xml
index 3e2e2151..cc2e5c3d 100644
--- a/rsql-common/pom.xml
+++ b/rsql-common/pom.xml
@@ -37,6 +37,12 @@
hamcrest
test
+
+ io.hypersistence
+ hypersistence-utils-hibernate-62
+ 3.5.1
+ test
+
com.h2database
h2
diff --git a/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java b/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java
index 2f78f765..8391b7d9 100644
--- a/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java
+++ b/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java
@@ -12,7 +12,9 @@
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.PluralAttribute;
+import lombok.Getter;
import org.springframework.core.convert.support.ConfigurableConversionService;
+import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.StringUtils;
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
@@ -26,6 +28,7 @@ public abstract class RSQLVisitorBase implements RSQLVisitor {
protected static volatile @Setter Map managedTypeMap;
protected static volatile @Setter Map entityManagerMap;
+ protected static volatile @Setter @Getter Map entityManagerDatabase = new HashMap();
protected static final Map primitiveToWrapper;
protected static volatile @Setter Map, Map> propertyRemapping;
protected static volatile @Setter Map, List> globalPropertyWhitelist;
diff --git a/rsql-jpa-spring-boot-starter/src/main/java/io/github/perplexhub/rsql/RSQLJPAAutoConfiguration.java b/rsql-jpa-spring-boot-starter/src/main/java/io/github/perplexhub/rsql/RSQLJPAAutoConfiguration.java
index d4ada2e1..644883ea 100644
--- a/rsql-jpa-spring-boot-starter/src/main/java/io/github/perplexhub/rsql/RSQLJPAAutoConfiguration.java
+++ b/rsql-jpa-spring-boot-starter/src/main/java/io/github/perplexhub/rsql/RSQLJPAAutoConfiguration.java
@@ -1,24 +1,122 @@
package io.github.perplexhub.rsql;
-import java.util.Map;
+import io.github.perplexhub.rsql.RSQLJPAAutoConfiguration.HibernateEntityManagerDatabaseConfiguration;
import javax.persistence.EntityManager;
-
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import lombok.extern.slf4j.Slf4j;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.dialect.DB2Dialect;
+import org.hibernate.dialect.DerbyDialect;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.dialect.H2Dialect;
+import org.hibernate.dialect.HSQLDialect;
+import org.hibernate.dialect.MySQLDialect;
+import org.hibernate.dialect.OracleDialect;
+import org.hibernate.dialect.PostgreSQLDialect;
+import org.hibernate.dialect.SQLServerDialect;
+import org.hibernate.dialect.SybaseDialect;
+import org.hibernate.engine.spi.SessionImplementor;
+import org.hibernate.internal.SessionFactoryImpl;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-
-import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Import;
+import org.springframework.orm.jpa.vendor.Database;
+import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Configuration
@ConditionalOnClass(EntityManager.class)
+@Import(HibernateEntityManagerDatabaseConfiguration.class)
public class RSQLJPAAutoConfiguration {
- @Bean
- public RSQLCommonSupport rsqlCommonSupport(Map entityManagerMap) {
- log.info("RSQLJPAAutoConfiguration.rsqlCommonSupport(entityManagerMap:{})", entityManagerMap.size());
- return new RSQLCommonSupport(entityManagerMap);
- }
+ @Bean
+ public RSQLCommonSupport rsqlCommonSupport(Map entityManagerMap,
+ ObjectProvider entityManagerDatabaseProvider) {
+ log.info("RSQLJPAAutoConfiguration.rsqlCommonSupport(entityManagerMap:{})", entityManagerMap.size());
+ EntityManagerDatabase entityManagerDatabase = entityManagerDatabaseProvider.getIfAvailable(() -> new EntityManagerDatabase(new HashMap()));
+
+ return new RSQLJPASupport(entityManagerMap, entityManagerDatabase.value());
+ }
+
+ @Configuration
+ @ConditionalOnClass(SessionImplementor.class)
+ static
+ class HibernateEntityManagerDatabaseConfiguration {
+
+ @Transactional
+ @Bean
+ public EntityManagerDatabase entityManagerDatabase(ObjectProvider entityManagers) {
+ Map value = new HashMap<>();
+ EntityManager entityManager = entityManagers.getIfAvailable();
+ SessionFactory sessionFactory = entityManager.unwrap(Session.class).getSessionFactory();
+ Dialect dialect = ((SessionFactoryImpl) sessionFactory).getJdbcServices().getDialect();
+
+ Database db = toDatabase(dialect);
+ if (db != null) {
+ value.put(entityManager, db);
+ }
+
+ return new EntityManagerDatabase(value);
+ }
+
+ private Database toDatabase(Dialect dialect) {
+ if (dialect instanceof PostgreSQLDialect) {
+ return Database.POSTGRESQL;
+ } else if (dialect instanceof MySQLDialect) {
+ return Database.MYSQL;
+ } else if (dialect instanceof SQLServerDialect) {
+ return Database.SQL_SERVER;
+ } else if (dialect instanceof OracleDialect) {
+ return Database.ORACLE;
+ } else if (dialect instanceof DerbyDialect) {
+ return Database.DERBY;
+ } else if (dialect instanceof DB2Dialect) {
+ return Database.DB2;
+ } else if (dialect instanceof H2Dialect) {
+ return Database.H2;
+ } else if (dialect instanceof HSQLDialect) {
+ return Database.HSQL;
+ } else if (dialect instanceof SybaseDialect) {
+ return Database.SQL_SERVER;
+ }
+
+ return null;
+ }
+ }
+
+ public static final class EntityManagerDatabase {
+ private final Map value;
+
+ public EntityManagerDatabase(Map value) {
+ this.value = value;
+ }
+
+ public Map value() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ EntityManagerDatabase that = (EntityManagerDatabase) obj;
+ return Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+ @Override
+ public String toString() {
+ return "EntityManagerDatabase[value=" + value + "]";
+ }
+ }
}
diff --git a/rsql-jpa/pom.xml b/rsql-jpa/pom.xml
index e21f5f55..dd804dd0 100644
--- a/rsql-jpa/pom.xml
+++ b/rsql-jpa/pom.xml
@@ -41,6 +41,34 @@
h2
test
+
+ org.postgresql
+ postgresql
+ test
+
+
+ org.testcontainers
+ postgresql
+ 1.19.0
+ test
+
+
+ org.hibernate.common
+ hibernate-commons-annotations
+ 5.1.2.Final
+
+
+ io.hypersistence
+ hypersistence-utils-hibernate-52
+ 3.7.6
+ test
+
+
+ net.java.dev.jna
+ jna
+ 5.14.0
+ test
+
org.projectlombok
lombok
diff --git a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java
index 6b587f96..be73a97f 100644
--- a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java
+++ b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java
@@ -2,6 +2,8 @@
import static io.github.perplexhub.rsql.RSQLOperators.*;
+import javax.persistence.Column;
+import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
@@ -18,14 +20,18 @@
import cz.jirutka.rsql.parser.ast.ComparisonNode;
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
import cz.jirutka.rsql.parser.ast.OrNode;
+import java.util.stream.Stream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
+import org.springframework.orm.jpa.vendor.Database;
@Slf4j
@SuppressWarnings({ "rawtypes", "unchecked" })
public class RSQLJPAPredicateConverter extends RSQLVisitorBase {
+ private static final Set JSON_SUPPORT = EnumSet.of(Database.POSTGRESQL);
+
private final CriteriaBuilder builder;
private final Map cachedJoins = new HashMap<>();
private final @Getter Map propertyPathMapper;
@@ -134,6 +140,10 @@ RSQLJPAContext findPropertyPath(String propertyPath, Path startRoot) {
String keyJoin = getKeyJoin(root, mappedProperty);
log.debug("Create a element collection join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
root = join(keyJoin, root, mappedProperty);
+ } else if (isJsonType(mappedProperty, classMetadata)) {
+ root = root.get(mappedProperty);
+ attribute = classMetadata.getAttribute(mappedProperty);
+ break;
} else {
log.debug("Create property path for type [{}] property [{}]", classMetadata.getJavaType().getName(), mappedProperty);
root = root.get(mappedProperty);
@@ -157,6 +167,35 @@ RSQLJPAContext findPropertyPath(String propertyPath, Path startRoot) {
return RSQLJPAContext.of(root, attribute, classMetadata);
}
+ private boolean isJsonType(String mappedProperty, ManagedType> classMetadata) {
+ return Optional.ofNullable(classMetadata.getAttribute(mappedProperty))
+ .map(this::isJsonType)
+ .orElse(false);
+ }
+
+ private boolean isJsonType(Attribute, ?> attribute) {
+ return isJsonColumn(attribute) && getDatabase(attribute).map(JSON_SUPPORT::contains).orElse(false);
+ }
+
+ private boolean isJsonColumn(Attribute, ?> attribute) {
+ return Optional.ofNullable(attribute)
+ .filter(attr -> attr.getJavaMember() instanceof Field)
+ .map(attr -> ((Field) attr.getJavaMember()))
+ .map(field -> field.getAnnotation(Column.class))
+ .map(Column::columnDefinition)
+ .map("jsonb"::equalsIgnoreCase)
+ .orElse(false);
+ }
+
+ private Optional getDatabase(Attribute, ?> attribute) {
+ return getEntityManagerMap()
+ .values()
+ .stream()
+ .filter(em -> em.getMetamodel().getManagedTypes().contains(attribute.getDeclaringType()))
+ .findFirst()
+ .map(em -> getEntityManagerDatabase().get(em));
+ }
+
private String getKeyJoin(Path> root, String mappedProperty) {
return root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
}
@@ -195,7 +234,9 @@ public Predicate visit(ComparisonNode node, From root) {
return customPredicate.getConverter().apply(RSQLCustomPredicateInput.of(builder, attrPath, attribute, arguments, root));
}
- Class type = attribute != null ? attribute.getJavaType() : null;
+ final boolean json = isJsonType(attribute);
+ Class type = json ? String.class : attribute != null ? attribute.getJavaType() : null;
+
if (attribute != null) {
if (attribute.getPersistentAttributeType() == PersistentAttributeType.ELEMENT_COLLECTION) {
type = getElementCollectionGenericType(type, attribute);
@@ -206,6 +247,7 @@ public Predicate visit(ComparisonNode node, From root) {
type = RSQLJPASupport.getValueTypeMap().get(type); // if you want to treat Enum as String and apply like search, etc
}
}
+ Expression expr = json ? getJsonExpression(attrPath, attribute, node) : attrPath;
if (node.getArguments().size() > 1) {
List