Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
<java.version>17</java.version>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.0.0</spring-boot.version>
<spring-data-releasetrain.version>2022.0.0</spring-data-releasetrain.version>
<spring-boot.version>3.4.1</spring-boot.version>
<spring-data-releasetrain.version>2024.1.1</spring-data-releasetrain.version>
<querydsl.version>4.1.4</querydsl.version>
<rsql-parser.version>2.3.2</rsql-parser.version>
<lombok.version>1.18.24</lombok.version>
Expand Down
4 changes: 2 additions & 2 deletions rsql-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
</dependency>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-62</artifactId>
<version>3.5.1</version>
<artifactId>hypersistence-utils-hibernate-63</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.perplexhub.rsql;

import java.lang.reflect.*;
import java.sql.Timestamp;
import java.time.*;
import java.time.format.DateTimeParseException;
import java.util.*;
Expand All @@ -13,6 +14,7 @@
import jakarta.persistence.metamodel.PluralAttribute;

import lombok.Getter;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.orm.jpa.vendor.Database;
Expand Down Expand Up @@ -52,6 +54,15 @@ public static Database getDatabase(EntityManager entityManager) {
return entityManagerDatabase.get(entityManager);
}

public static <T> Attribute<? super T, ?> getAttribute(String property, ManagedType<T> classMetadata) {
// W/A found here: https://hibernate.atlassian.net/browse/HHH-18569
// breaking change on hibernate side: https://github.com/hibernate/hibernate-orm/pull/6924#discussion_r1250474422
if (classMetadata instanceof ManagedDomainType managedDomainType) {
return managedDomainType.findSubTypesAttribute(property);
}
return classMetadata.getAttribute(property);
}

protected abstract Map<String, String> getPropertyPathMapper();

public Map<Class<?>, Map<String, String>> getPropertyRemapping() {
Expand All @@ -71,6 +82,9 @@ protected Object convert(String source, Class targetType) {
object = UUID.fromString(source);
} else if (targetType.equals(Date.class) || targetType.equals(java.sql.Date.class)) {
object = java.sql.Date.valueOf(LocalDate.parse(source));
} else if (targetType.equals(Timestamp.class)) {
Date date = java.sql.Date.valueOf(LocalDate.parse(source));
return new Timestamp(date.getTime());
} else if (targetType.equals(LocalDate.class)) {
object = LocalDate.parse(source);
} else if (targetType.equals(LocalDateTime.class)) {
Expand Down Expand Up @@ -158,10 +172,10 @@ protected String mapProperty(String selector, Class<?> entityClass) {

protected <T> Class<?> findPropertyType(String property, ManagedType<T> classMetadata) {
Class<?> propertyType = null;
if (classMetadata.getAttribute(property).isCollection()) {
propertyType = ((PluralAttribute) classMetadata.getAttribute(property)).getBindableJavaType();
if (getAttribute(property, classMetadata).isCollection()) {
propertyType = ((PluralAttribute) getAttribute(property, classMetadata)).getBindableJavaType();
} else {
propertyType = classMetadata.getAttribute(property).getJavaType();
propertyType = getAttribute(property, classMetadata).getJavaType();
}
return propertyType;
}
Expand Down Expand Up @@ -217,7 +231,7 @@ protected <T> ManagedType<T> getManagedElementCollectionType(String mappedProper

protected <T> boolean hasPropertyName(String property, ManagedType<T> classMetadata) {
try {
return classMetadata.getAttribute(property) != null;
return getAttribute(property, classMetadata) != null;
} catch (IllegalArgumentException e) {
return false;
}
Expand All @@ -238,30 +252,30 @@ protected static Class getElementCollectionGenericType(Class type, Attribute att
}

protected <T> boolean isEmbeddedType(String property, ManagedType<T> classMetadata) {
return classMetadata.getAttribute(property).getPersistentAttributeType() == PersistentAttributeType.EMBEDDED;
return getAttribute(property, classMetadata).getPersistentAttributeType() == PersistentAttributeType.EMBEDDED;
}

protected <T> boolean isElementCollectionType(String property, ManagedType<T> classMetadata) {
return classMetadata.getAttribute(property).getPersistentAttributeType() == PersistentAttributeType.ELEMENT_COLLECTION;
return getAttribute(property, classMetadata).getPersistentAttributeType() == PersistentAttributeType.ELEMENT_COLLECTION;
}

protected <T> boolean isAssociationType(String property, ManagedType<T> classMetadata) {
return classMetadata.getAttribute(property).isAssociation();
return getAttribute(property, classMetadata).isAssociation();
}

protected <T> boolean isOneToOneAssociationType(String property, ManagedType<T> classMetadata) {
return classMetadata.getAttribute(property).isAssociation()
&& PersistentAttributeType.ONE_TO_ONE == classMetadata.getAttribute(property).getPersistentAttributeType();
return getAttribute(property, classMetadata).isAssociation()
&& PersistentAttributeType.ONE_TO_ONE == getAttribute(property, classMetadata).getPersistentAttributeType();
}

protected <T> boolean isOneToManyAssociationType(String property, ManagedType<T> classMetadata) {
return classMetadata.getAttribute(property).isAssociation()
&& PersistentAttributeType.ONE_TO_MANY == classMetadata.getAttribute(property).getPersistentAttributeType();
return getAttribute(property, classMetadata).isAssociation()
&& PersistentAttributeType.ONE_TO_MANY == getAttribute(property, classMetadata).getPersistentAttributeType();
}

protected <T> boolean isManyToManyAssociationType(String property, ManagedType<T> classMetadata) {
return classMetadata.getAttribute(property).isAssociation()
&& PersistentAttributeType.MANY_TO_MANY == classMetadata.getAttribute(property).getPersistentAttributeType();
return getAttribute(property, classMetadata).isAssociation()
&& PersistentAttributeType.MANY_TO_MANY == getAttribute(property, classMetadata).getPersistentAttributeType();
}

static {
Expand Down
4 changes: 2 additions & 2 deletions rsql-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
</dependency>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-60</artifactId>
<version>3.5.2</version>
<artifactId>hypersistence-utils-hibernate-63</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import cz.jirutka.rsql.parser.ast.OrNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.query.criteria.JpaExpression;

@Slf4j
@SuppressWarnings({ "rawtypes", "unchecked" })
Expand Down Expand Up @@ -81,7 +82,6 @@ private RSQLJPAContext findPropertyPathInternal(String propertyPath, Path startR
Attribute<?, ?> attribute = null;
String resolvedPropertyPath = firstTry? mapPropertyPath(propertyPath) : propertyPath;
String[] properties = mapPropertyPath(resolvedPropertyPath).split("\\.");

for (int i = 0, propertiesLength = properties.length; i < propertiesLength; i++) {
String property = properties[i];
String mappedProperty = mapProperty(property, classMetadata.getJavaType());
Expand Down Expand Up @@ -138,14 +138,14 @@ private RSQLJPAContext findPropertyPathInternal(String propertyPath, Path startR
}
} else if (isElementCollectionType(mappedProperty, classMetadata)) {
String previousClass = classMetadata.getJavaType().getName();
attribute = classMetadata.getAttribute(property);
attribute = RSQLVisitorBase.getAttribute(property, classMetadata);
classMetadata = getManagedElementCollectionType(mappedProperty, classMetadata);
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 (JsonbSupport.isJsonType(mappedProperty, classMetadata)) {
root = root.get(mappedProperty);
attribute = classMetadata.getAttribute(mappedProperty);
attribute = RSQLVisitorBase.getAttribute(mappedProperty, classMetadata);
break;
} else {
log.debug("Create property path for type [{}] property [{}]", classMetadata.getJavaType().getName(), mappedProperty);
Expand All @@ -156,7 +156,7 @@ private RSQLJPAContext findPropertyPathInternal(String propertyPath, Path startR
type = embeddedType;
classMetadata = getManagedType(embeddedType);
} else {
attribute = classMetadata.getAttribute(property);
attribute = RSQLVisitorBase.getAttribute(property, classMetadata);
}
}
}
Expand Down Expand Up @@ -244,7 +244,13 @@ private ResolvedExpression resolveExpression(ComparisonNode node, From root, Sel
ComparisonNode jsonbNode = node.withSelector(jsonbPath);
return JsonbSupport.jsonbPathExistsExpression(builder, jsonbNode, path);
} else {
return ResolvedExpression.ofPath(path.as(String.class), String.class);
final Expression expression;
if (path instanceof JpaExpression jpaExpression) {
expression = jpaExpression.cast(String.class);
} else {
expression = path.as(String.class);
}
return ResolvedExpression.ofPath(expression, String.class);
}
} else {
if (attribute != null
Expand Down
16 changes: 11 additions & 5 deletions rsql-jpa/src/main/java/io/github/perplexhub/rsql/SortUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Root;

import org.hibernate.query.criteria.JpaExpression;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

Expand Down Expand Up @@ -63,16 +64,16 @@ private static Order sortToJpaOrder(final String[] parts, final SortSupport sort
sortSupport.getJoinHints(), sortSupport.getProcedureWhiteList(),
sortSupport.getProcedureBlackList());

final boolean ic = parts.length > 2 && "ic".equalsIgnoreCase(parts[2]);
Expression<?> propertyExpression = selector.getExpression((string, builder) ->{
final RSQLJPAContext rsqljpaContext = converter.findPropertyPath(string, root);
final boolean isJson = JsonbSupport.isJsonType(rsqljpaContext.getAttribute());
return isJson
? sortExpressionOfJson(rsqljpaContext, string, sortSupport.getPropertyPathMapper(), builder)
? sortExpressionOfJson(rsqljpaContext, string, sortSupport.getPropertyPathMapper(), builder, ic)
: rsqljpaContext.getPath();
});

if (parts.length > 2 && "ic".equalsIgnoreCase(parts[2])
&& String.class.isAssignableFrom(propertyExpression.getJavaType())) {
if (ic && String.class.isAssignableFrom(propertyExpression.getJavaType())) {
propertyExpression = cb.lower(propertyExpression.as(String.class));
}

Expand All @@ -92,7 +93,8 @@ private static Order sortToJpaOrder(final String[] parts, final SortSupport sort
private static Expression<?> sortExpressionOfJson(RSQLJPAContext context,
String property,
Map<String, String> mapping,
CriteriaBuilder builder) {
CriteriaBuilder builder,
boolean ic) {
String path = PathUtils.expectBestMapping(property, mapping);
String jsonbSelector = JsonbSupport.jsonPathOfSelector(context.getAttribute(), path);
if(jsonbSelector.contains(".")) {
Expand All @@ -102,7 +104,11 @@ private static Expression<?> sortExpressionOfJson(RSQLJPAContext context,
.skip(1) // skip root
.map(builder::literal)
.forEach(args::add);
return builder.function("jsonb_extract_path", String.class, args.toArray(Expression[]::new));
Expression<?> expression = builder.function("jsonb_extract_path", Object.class, args.toArray(Expression[]::new));
if (ic && expression instanceof JpaExpression<?> jpaExpression) {
expression = jpaExpression.cast(String.class);
}
return expression;
} else {
return context.getPath().as(String.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ && isJsonColumn(attribute)
* @return true if the attribute is a jsonb attribute
*/
public static boolean isJsonType(String mappedProperty, ManagedType<?> classMetadata) {
return Optional.ofNullable(classMetadata.getAttribute(mappedProperty))
return Optional.ofNullable(RSQLVisitorBase.getAttribute(mappedProperty, classMetadata))
.map(JsonbSupport::isJsonType)
.orElse(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.*;
Expand Down Expand Up @@ -764,9 +766,9 @@ final void testBetweenDate() {
@Test
final void testBetweenDateTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
RSQLJPASupport.addConverter(Date.class, s -> {
RSQLJPASupport.addConverter(Timestamp.class, s -> {
try {
return sdf.parse(s);
return new Timestamp(sdf.parse(s).getTime());
} catch (ParseException e) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.perplexhub.rsql;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;

Expand All @@ -23,9 +24,9 @@ public static void main(String[] args) throws Exception {
@Bean
public Object rsqlConfiguration(RSQLCommonSupport rsqlCommonSupport) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
RSQLCommonSupport.addConverter(Date.class, s -> {
RSQLCommonSupport.addConverter(Timestamp.class, s -> {
try {
return sdf.parse(s);
return new Timestamp(sdf.parse(s).getTime());
} catch (Exception e) {
return null;
}
Expand Down
Loading