Coder Social home page Coder Social logo

perplexhub / rsql-jpa-specification Goto Github PK

View Code? Open in Web Editor NEW
222.0 8.0 61.0 580 KB

Java Library to Translate RSQL into Spring Data JPA Specification and QueryDSL Predicate

License: MIT License

Java 100.00%
querydsl-jpa querydsl spring-data-jpa spring-data-jpa-specification dynamic-querydsl dynamic-jpa rsql rsql-jpa-specification fiql-parser rsql-syntax

rsql-jpa-specification's Introduction

rsql-jpa-specification

Sonatype Nexus (Releases) Sonatype Nexus (Snapshots)

Release Workflow Status Snapshot Workflow Status PR Workflow Status

Translate RSQL query into org.springframework.data.jpa.domain.Specification or com.querydsl.core.types.Predicate and support entities association query.

SpringBoot 3 Support

rsql-jpa-specification supports SpringBoot 3 since version 6.x. (Contributed by chriseteka)

For SpringBoot 2 users, please continue to use version 5.x.

Supported Operators

Since version 5.0.5, you can define your own operators and customize the logic via RSQLCustomPredicate.

Maven Repository

https://oss.sonatype.org/#nexus-search;gav~io.github.perplexhub~rsql*

Add rsql-jpa-spring-boot-starter for RSQL to Spring JPA translation

Maven dependency for rsql-jpa-spring-boot-starter

  <dependency>
    <groupId>io.github.perplexhub</groupId>
    <artifactId>rsql-jpa-spring-boot-starter</artifactId>
    <version>X.X.X</version>
  </dependency>

Add JpaSpecificationExecutor to your JPA repository interface classes

package com.perplexhub.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import com.perplexhub.model.User;

public interface UserRepository extends JpaRepository<User, String>, JpaSpecificationExecutor<User> {
}

Sample main class - Application.java

package io.github.perplexhub.rsql;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableJpaRepositories(basePackages = { "io.github.xxx.yyy.repository" })
@EnableTransactionManagement
@SpringBootApplication
public class Application {

	public static void main(String[] args) throws Exception {
		SpringApplication.run(Application.class, args);
	}

}

Add rsql-querydsl-spring-boot-starter for RSQL to Spring JPA and QueryDSL translation

Maven dependency for rsql-querydsl-spring-boot-starter

  <dependency>
    <groupId>io.github.perplexhub</groupId>
    <artifactId>rsql-querydsl-spring-boot-starter</artifactId>
    <version>X.X.X</version>
  </dependency>

Add JpaSpecificationExecutor and QuerydslPredicateExecutor to your JPA repository interface classes

package com.perplexhub.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;

import com.perplexhub.model.User;

public interface UserRepository extends JpaRepository<User, String>, JpaSpecificationExecutor<User>, QuerydslPredicateExecutor<User> {
}

Use below properties to control the version of Spring Boot, Spring Data and QueryDSL

  <properties>
    <spring-boot.version>3.0.0</spring-boot.version>
    <spring-data-releasetrain.version>2022.0.0</spring-data-releasetrain.version>
    <querydsl.version>4.1.4</querydsl.version>
  </properties>

RSQL Syntax Reference

filter = "id=bt=(2,4)";// id>=2 && id<=4 //between
filter = "id=nb=(2,4)";// id<2 || id>4 //not between
filter = "company.code=like=em"; //like %em%
filter = "company.code=ilike=EM"; //ignore case like %EM%
filter = "company.code=icase=EM"; //ignore case equal EM
filter = "company.code=notlike=em"; //not like %em%
filter = "company.code=inotlike=EM"; //ignore case not like %EM%
filter = "company.code=ke=e*m"; //like %e*m%
filter = "company.code=ik=E*M"; //ignore case like %E*M%
filter = "company.code=nk=e*m"; //not like %e*m%
filter = "company.code=ni=E*M"; //ignore case not like %E*M%
filter = "company.code=ic=E^^M"; //ignore case equal E^^M
filter = "company.code==demo"; //equal
filter = "company.code=='demo'"; //equal
filter = "company.code==''"; //equal to empty string
filter = "company.code==dem*"; //like dem%
filter = "company.code==*emo"; //like %emo
filter = "company.code==*em*"; //like %em%
filter = "company.code==^EM"; //ignore case equal EM
filter = "company.code==^*EM*"; //ignore case like %EM%
filter = "company.code=='^*EM*'"; //ignore case like %EM%
filter = "company.code!=demo"; //not equal
filter = "company.code=in=(*)"; //equal to *
filter = "company.code=in=(^)"; //equal to ^
filter = "company.code=in=(demo,real)"; //in
filter = "company.code=out=(demo,real)"; //not in
filter = "company.id=gt=100"; //greater than
filter = "company.id=lt=100"; //less than
filter = "company.id=ge=100"; //greater than or equal
filter = "company.id=le=100"; //less than or equal
filter = "company.id>100"; //greater than
filter = "company.id<100"; //less than
filter = "company.id>=100"; //greater than or equal
filter = "company.id<=100"; //less than or equal
filter = "company.code=isnull="; //is null
filter = "company.code=null="; //is null
filter = "company.code=na="; //is null
filter = "company.code=nn="; //is not null
filter = "company.code=notnull="; //is not null
filter = "company.code=isnotnull="; //is not null

filter = "company.code=='demo';company.id>100"; //and
filter = "company.code=='demo' and company.id>100"; //and

filter = "company.code=='demo',company.id>100"; //or
filter = "company.code=='demo' or company.id>100"; //or

Syntax Reference: RSQL / FIQL parser

Spring Data JPA Specification

Pageable pageable = PageRequest.of(0, 5); //page 1 and page size is 5

repository.findAll(RSQLSupport.toSpecification(filter));
repository.findAll(RSQLSupport.toSpecification(filter), pageable);

repository.findAll(RSQLSupport.toSpecification(filter, true)); // select distinct
repository.findAll(RSQLSupport.toSpecification(filter, true), pageable);

// use static import
import static io.github.perplexhub.rsql.RSQLSupport.*;

repository.findAll(toSpecification(filter));
repository.findAll(toSpecification(filter), pageable);

repository.findAll(toSpecification(filter, true)); // select distinct
repository.findAll(toSpecification(filter, true), pageable);

// property path remap
filter = "compCode=='demo';compId>100"; // "company.code=='demo';company.id>100" -  protect our domain model #10

Map<String, String> propertyPathMapper = new HashMap<>();
propertyPathMapper.put("compId", "company.id");
propertyPathMapper.put("compCode", "company.code");

repository.findAll(toSpecification(filter, propertyPathMapper));
repository.findAll(toSpecification(filter, propertyPathMapper), pageable);

Sort Syntax

sort = "id"; // order by id asc
sort = "id,asc"; // order by id asc
sort = "id,asc;company.id,desc"; // order by id asc, company.id desc
sort = "name,asc,ic"  // order by name ascending ignore case

Sort with JPA Specifications

repository.findAll(RSQLSupport.toSort("id,asc;company.id,desc"));

// sort with custom field mapping
Map<String, String> propertyMapping = new HashMap<>();
propertyMapping.put("userID", "id");
propertyMapping.put("companyID", "company.id");

repository.findAll(RSQLSupport.toSort("userID,asc;companyID,desc", propertyMapping)); // same as id,asc;company.id,desc

Filtering and Sorting with JPA Specification

Specification<?> specification = RSQLSupport.toSpecification("company.name==name")
    .and(RSQLSupport.toSort("company.name,asc,ic;user.id,desc"));

repository.findAll(specification);

QueryDSL Predicate (BooleanExpression)

Pageable pageable = PageRequest.of(0, 5); //page 1 and page size is 5

repository.findAll(RSQLSupport.toPredicate(filter, QUser.user));
repository.findAll(RSQLSupport.toPredicate(filter, QUser.user), pageable);

// use static import
import static io.github.perplexhub.rsql.RSQLSupport.*;

repository.findAll(toPredicate(filter, QUser.user));
repository.findAll(toPredicate(filter, QUser.user), pageable);

// property path remap
filter = "compCode=='demo';compId>100"; // "company.code=='demo';company.id>100" - protect our domain model #10

Map<String, String> propertyPathMapper = new HashMap<>();
propertyPathMapper.put("compId", "company.id");
propertyPathMapper.put("compCode", "company.code");

repository.findAll(toPredicate(filter, QUser.user, propertyPathMapper));
repository.findAll(toPredicate(filter, QUser.user, propertyPathMapper), pageable);

Custom Value Converter

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
RSQLJPASupport.addConverter(Date.class, s -> {
	try {
		return sdf.parse(s);
	} catch (ParseException e) {
		return null;
	}
});

Custom Operator & Predicate

String rsql = "createDate=dayofweek='2'";
RSQLCustomPredicate<Long> customPredicate = new RSQLCustomPredicate<>(new ComparisonOperator("=dayofweek="), Long.class, input -> {
	Expression<Long> function = input.getCriteriaBuilder().function("ISO_DAY_OF_WEEK", Long.class, input.getPath());
	return input.getCriteriaBuilder().lessThan(function, (Long) input.getArguments().get(0));
});
List<User> users = userRepository.findAll(toSpecification(rsql, Arrays.asList(customPredicate)));
String rsql = "name=around='May'";
RSQLCustomPredicate<String> customPredicate = new RSQLCustomPredicate<>(new ComparisonOperator("=around="), String.class, input -> {
	if ("May".equals(input.getArguments().get(0))) {
		return input.getPath().in(Arrays.asList("April", "May", "June"));
	}
	return input.getCriteriaBuilder().equal(input.getPath(), (String) input.getArguments().get(0));
});
List<User> users = userRepository.findAll(toSpecification(rsql, Arrays.asList(customPredicate)));
String rsql = "company.id=between=(2,3)";
RSQLCustomPredicate<Long> customPredicate = new RSQLCustomPredicate<>(new ComparisonOperator("=between=", true), Long.class, input -> {
	return input.getCriteriaBuilder().between(input.getPath().as(Long.class), (Long) input.getArguments().get(0), (Long) input.getArguments().get(1));
});
List<User> users = userRepository.findAll(toSpecification(rsql, Arrays.asList(customPredicate)));
String rsql = "city=notAssigned=''";
RSQLCustomPredicate<String> customPredicate = new RSQLCustomPredicate<>(new ComparisonOperator("=notAssigned="), String.class, input -> {
	return input.getCriteriaBuilder().isNull(input.getRoot().get("city"));
});
List<User> users = userRepository.findAll(toSpecification(rsql, Arrays.asList(customPredicate)));

Escaping Special Characters in LIKE Predicate

For the LIKE statement in different RDBMS, the most commonly used special characters are:

MySQL/MariaDB and PostgreSQL

  • %: Represents any sequence of zero or more characters. For example, LIKE '%abc' would match any string ending with abc.
  • _: Represents any single character. For example, LIKE 'a_c' would match a three-character string starting with a and ending with c.

SQL Server

  • % and _: Function in the same way as in MySQL/MariaDB and PostgreSQL.
  • []: Used to specify a set or range of characters. For instance, LIKE '[a-c]%' would match any string starting with a, b, or c.
  • ^: Used within [] to exclude characters. For example, LIKE '[^a-c]%' would match any string not starting with a, b, or c.

Oracle:

  • % and _: Function similarly to MySQL/MariaDB and PostgreSQL.
  • ESCAPE: Allows specifying an escape character to include % or _ literally in the search. For example, LIKE '%\_%' ESCAPE '\' would match a string containing an underscore.

LIKE in RSQL

To use escape character in RSQL, you must use QuerySupport to build the Specification with appropriate escape character.

char escapeChar = '$';
QuerySupport query = QuerySupport.builder()
    .rsqlQuery("name=like='" + escapeChar + "%'")
    .likeEscapeCharacter(escapeChar)
    .build();
List<Company> users = companyRepository.findAll(toSpecification(query));

Example

Above RSQL with default escape character $ for searching string containing _:

my_table.my_column=like='$_'

Will produce the following SQL:

SELECT * FROM my_table WHERE my_column LIKE '%$_%' ESCAPE '$'

Jsonb Support with Postgresql

It's possible to make rsql queries on jsonb fields. For example, if you have a jsonb field named data in your entity, you can make queries like this:

{
  "data": {
    "name": "demo",
    "user" : {
      "id": 1,
      "name": "demo"
    },
    "roles": [
      {
        "id": 1,
        "name": "admin"
      },
      {
        "id": 2,
        "name": "user"
      }
    ]
  }
}
String rsql = "data.name==demo";
List<User> users = userRepository.findAll(toSpecification(rsql));
String rsql = "data.user.id==1";
List<User> users = userRepository.findAll(toSpecification(rsql));
String rsql = "data.roles.id==1";
List<User> users = userRepository.findAll(toSpecification(rsql));

The library use jsonb_path_exists function under the hood.
Json primitive types are supported such as

  • string
  • number
  • boolean
  • array

Temporal values support

Since Postgresql 13 jsonb supports temporal values with datetime() function.
As Date time values are string in jsonb, you can make queries on them as well.
You must use the ISO 8601 format for date time values.

If your request conform timezone pattern, the library will use `jsonb_path_exists_tz.
Then consider the timezone consideration of the official documentation

Stored procedure

RSQL can call a stored procedure with the following syntax for both search and sort.
In order to be authorized to call a stored procedure, it must be whitelisted and not blacklisted.
The only way to whitelist or blacklist a stored procedure is to use the QuerySupport when performing the search or the SortSupport when performing the sort.

String rsql = "@concat[greetings|#123]=='HELLO123'";
QuerySupport querySupport = QuerySupport.builder()
        .rsqlQuery(rsql)
        .procedureWhiteList(List.of("concat", "upper"))
        .build();
List<Item> companies = itemRepository.findAll(toSpecification(querySupport));

Regex like expression can be used to whitelist or blacklist stored procedure.

Syntax

A procedure must be prefixed with @ and called with [] for arguments.

@procedure_name[arg1|arg2|...]

Arguments

Arguments are separated by | and can be:

  • constant (null, boolean, number, string), prefixed with #
  • column name
  • other procedure call
@procedure_name[arg1|arg2|...]
@procedure_name[column1|column2|...]
@procedure_name[@function_name[arg1|arg2|...]|column1|#textvalue|#123|#true|#false|#null]

For text value, since space is not supported by RSQL, you can use \t to replace space.

Usage

Search

String rsql1 = "@upper[code]==HELLO";
String rsql2 = "@concat[@upper[code]|name]=='TESTTest Lab'";
String rsql3 = "@concat[@upper[code]|#123]=='HELLO123'";

Sort

String sort1 = "@upper[code],asc";
String sort2 = "@concat[@upper[code]|name],asc";
String sort3 = "@concat[@upper[code]|#123],asc";

rsql-jpa-specification's People

Contributors

almalerik avatar borsch avatar bukajsytlos avatar chriseteka avatar dependabot[bot] avatar gls-luis-alves avatar gpr-indevelopment avatar itineric avatar jasonmunchhof-msr avatar maxbettercloud avatar ng-galien avatar nstdio avatar perplexhub avatar pyguy2 avatar rakesh-roshan avatar spamba22 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rsql-jpa-specification's Issues

Root join on @OneToMany property, not working while sorting in SortUtils.pathToExpression(final Root<?> root, final String path)

Hello dear RSQL JPA Spec team,

Hoping that your day is going well. I have the next Exception:

"An unexpected error occurred: Illegal attempt to dereference path source [null.criteria] of basic type" while trying to sort by a mapped property called qualityTarget to criteria.qualityTarget.

  • 1st level, Root: Review.class
...
/** Criteria. */
  @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true,
    targetEntity = Criterion.class)
  private List<Criterion> criteria = new ArrayList<>();
...
  • 2nd Level: Criterion.class
...
  /** Quality target. */
  private String qualityTarget;

  /** Review. */
  @ManyToOne private Review review;
...
  • Issue
    Whenever I try to generate the Expression from the passed string path, it fails on line 51 on the screenshot from SortUtils.java

image

Could you please provide some insights, as we have the same approach in other classes that work, with the only difference that we only map properties to @OneToOne relations, and not @OneToMany like in this case with List<Criterion> criteria.

Thanks in advance for any help and wish you a cool day ahead,
Alan

Support Spring Boot 3

See this section in Preparing for Spring Boot 3.0.

Main issue should be the use of Jakarta EE 9 APIs (jakarta.) instead of EE 8 (javax.), e.g. javax.persistence.EntityManager should be jakarta.persistence.EntityManager.

Tried Spring Boot 3.0.0-M3 with the current release and it fails to launch with

java.lang.AbstractMethodError: Receiver class io.github.perplexhub.rsql.RSQLJPASupport$1 does not define or inherit an implementation of the resolved method &#39;abstract jakarta.persistence.criteria.Predicate toPredicate(jakarta.persistence.criteria.Root, jakarta.persistence.criteria.CriteriaQuery, jakarta.persistence.criteria.CriteriaBuilder)&#39; of interface org.springframework.data.jpa.domain.Specification.

Should RSQLCommonSupport.addPropertyWhitelist be static?

When we do:

RSQLCommonSupport.addPropertyWhitelist(UserEntity.class,
                List.of("username", "firstName", "lastName", "email", "active"));

This is applied global, correct?

How can I have 2 queries to the same user entity, for to different roles: USER and ADMIN where admin can have more complex RSQL, i.e. with more properties on UserEntity whitelisted?

Shouldn't this be possible to manage per query?

Custom Predicate With 2 Arguments

Hi,
Is there any way to create a custom predicate with two arguments?
I need to recreate de operator between, for use into dates because my database uses datetime type and I need to truncate the hour to work as expected.

public static RSQLCustomPredicate<Date> customPredicateBetweenDate() {
    String nameFunctionDatabase = "BETWEEN";

    String nameQueryUrl = "=dbt=";

    return new RSQLCustomPredicate<>(new ComparisonOperator( nameQueryUrl ), Date.class, input -> {
        Expression<Date> function = input.getCriteriaBuilder().function(nameFunctionDatabase , Date.class, input.getPath());
        return input.getCriteriaBuilder().between(function,(Date) input.getArguments().get(0), (Date) input.getArguments().get(1));
    });
}

The way above expects just one argument.

Filtering on optional one to many attribute does an inner join

Hi there. I am having an issue similar to issue #23. In this case I have an optional one to many relationship between an Inquiry object and an Owners collection. I want to filter with: "inquiry.id==66,inquiry.owners.name==66". I would expect to get any inquiries with id==66, regardless of whether the inquiry has any owners, and any inquiry whose owner list contains an owner with name==66. Instead, I will only get an inquiry that has id==66 if that inquiry also has an owner (records matching inquiry.owners.name==66 are returned as expected).

Looking at the code updates for issue 23, it looks like maybe similar changes need to made to the element collection case?

if (isAssociationType(mappedProperty, classMetadata)) {
    boolean isOneToOneAssociationType = isOneToOneAssociationType(mappedProperty, classMetadata);
    Class<?> associationType = findPropertyType(mappedProperty, classMetadata);
    type = associationType;
    String previousClass = classMetadata.getJavaType().getName();
    classMetadata = getManagedType(associationType);

    String keyJoin = root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
    log.debug("Create a join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
    root = isOneToOneAssociationType ? joinLeft(keyJoin, root, mappedProperty) : join(keyJoin, root, mappedProperty);
} else if (isElementCollectionType(mappedProperty, classMetadata)) {
	String previousClass = classMetadata.getJavaType().getName();
	attribute = classMetadata.getAttribute(property);
	classMetadata = getManagedElementCollectionType(mappedProperty, classMetadata);

	String keyJoin = root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
	log.debug("Create a element collection join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
	root = join(keyJoin, root, mappedProperty);
}  

On line 69 in RSQLJPAPredicateConverter.java it does a check for one to one relations to do a left join. Could a similar check be added for one-to-many relations?

Subquery in RSQLCustomPredicate

Hey, is it possible to use subquery when registering custom predicates?

for example I want to achieve:
SELECT * from table_a a WHERE EXISTS (SELECT 1 FROM table_b b WHERE ...)

class RSQLCustomPredicateInput does not have any CriteriaQuery that I can use for subqery.

eventually I would like to use input.getCriteriaBuilder().exists(subqery)

NPE in RsqlJpaConverter.visit

I just integrated this in a Spring Boot project and it worked just fine for one of the scenarios. Unfortunately I hit an issue in another case where I have a hierarchical parent/child relationship inside an entity class and your logic doesn't set an attribute for properties of associated type inside RsqlJpaSpecification.findPropertyPath.

Thanks for the good work!

Pass a Set<String> of allowed searchable properties

I would be useful to have the ability to pass in a Set of properties that the query string is allowed to search on. Throw an exception if the query string contains a property name that is not on the allowed list.

Exception hierarchy?

Hey @perplexhub, thanks for such a great library!

I've came across a usability problem that others might also have. Let's say we are creating specification from such query that have whitelisted, blacklisted or not found fields. There is no good way to distinguish the thrown exception because it's IllegalArgumentException in all three cases. I'm proposing to introduce the modest exception types to distinguish (and perhaps provide usefull information inside the thown exception) problems.

If you interested in this I could open a PR with required changes.

How to specify the attributes/columns to retrieve

Thanks for the fantastic library.
I played with it and see we can apply various filtering on data including extension via custom filters.
I am thinking is there a possibility also to specify the fields/columns I want to retrieve from database.
Is that feature available? Possible to add if not available.
That's gonna make this api even more compelling.

Null pointer exception when filtering for null on optional ManyToOne relation

I have a JPA entity that has an optional relationship to itself call 'parent'.
I want to filter and retrieve all entities without a parent using the isnull operator:
filter=parent=isnull=''
This filter however throws a null pointer exception in the RSQLJPAPredicateConverter.findPropertyPath method when it calls this.accessControl(type, attribute.getName());
because 'attribute' is never set.

I tried working around it by creating a filter where the id is not null but the parent.id is, but that didn't work because the code is doing an inner join:
filter=id=isnotnull='';parent.id=isnull=''
resulted in this sql:
select ..._ from pi_codes pitype0_ **inner** join pi_codes pitype1_ on pitype0_.parent=pitype1_.id where pitype0_.dtype='pi_type' and (pitype0_.id is not null) and (pitype1_.id is null)
Since I noticed you have another bug where the code does an inner join instead of a left one and tried using 5.0.6-snapshot but although I don't have an NPE anymore, I do not get any data back either.

duplicates

hi perplexhub

I'm using Spring Data JPA Specification
repository.findAll(RSQLSupport.toSpecification(filter));
but I'm getting multiple records (2) with the same id (primary key) from the query

regards
Arvit

Operator =in= with one argument

When I execute
localhost:8080/some_path/v1?rsql=id=in=(1)&limit=135&offset=0, I have error
Unknown operator: =in=; nested exception is java.lang.IllegalArgumentException: Unknown operator: =in=
But it's don't true. Operator =in= is known)

Issue Trying to Retrieve Nested Entities

I'm trying to use rsql-jpa-specification to expose the data in my entities through a REST api.

If i pass the following filter string "sites.id==2" into my service i get the expected results. However,
if i try accessing any nested entities i.e. a trunk by id within a site using a filter string "sites.id==2 and sites.trunks.id==2" i get the following exception:

message": "Illegal attempt to dereference path source [null.trunks] of basic type; nested exception is java.lang.IllegalStateException: Illegal attempt to dereference path source [null.trunks] of basic type",
"path": "/api/v1/trunkgroup/filters/sites.id==2%20and%20sites.trunks.id==2"

Full stacktrace:

java.lang.IllegalStateException: Illegal attempt to dereference path source [null.trunks] of basic type
at org.hibernate.query.criteria.internal.path.AbstractPathImpl.illegalDereference(AbstractPathImpl.java:82) ~[hibernate-core-5.3.11.Final.jar:5.3.11.Final]
at org.hibernate.query.criteria.internal.path.AbstractPathImpl.get(AbstractPathImpl.java:174) ~[hibernate-core-5.3.11.Final.jar:5.3.11.Final]
at io.github.perplexhub.rsql.RSQLJpaPredicateConverter.findPropertyPath(RSQLJpaPredicateConverter.java:76) ~[rsql-jpa-specification-4.0.12.jar:na]
at io.github.perplexhub.rsql.RSQLJpaPredicateConverter.visit(RSQLJpaPredicateConverter.java:95) ~[rsql-jpa-specification-4.0.12.jar:na]
at io.github.perplexhub.rsql.RSQLJpaPredicateConverter.visit(RSQLJpaPredicateConverter.java:30) ~[rsql-jpa-specification-4.0.12.jar:na]
at cz.jirutka.rsql.parser.ast.ComparisonNode.accept(ComparisonNode.java:70) ~[rsql-parser-2.1.0.jar:2.1.0]
at io.github.perplexhub.rsql.RSQLJpaPredicateConverter.lambda$visit$0(RSQLJpaPredicateConverter.java:212) ~[rsql-jpa-specification-4.0.12.jar:na]
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na]
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na]
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na]
at io.github.perplexhub.rsql.RSQLJpaPredicateConverter.visit(RSQLJpaPredicateConverter.java:212) ~[rsql-jpa-specification-4.0.12.jar:na]
at io.github.perplexhub.rsql.RSQLJpaPredicateConverter.visit(RSQLJpaPredicateConverter.java:30) ~[rsql-jpa-specification-4.0.12.jar:na]
at cz.jirutka.rsql.parser.ast.AndNode.accept(AndNode.java:42) ~[rsql-parser-2.1.0.jar:2.1.0]
at io.github.perplexhub.rsql.RSQLSupport$1.toPredicate(RSQLSupport.java:65) ~[rsql-jpa-specification-4.0.12.jar:na]
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applySpecificationToCriteria(SimpleJpaRepository.java:740) ~[spring-data-jpa-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:671) ~[spring-data-jpa-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:655) ~[spring-data-jpa-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:419) ~[spring-data-jpa-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295) ~[spring-tx-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ~[spring-tx-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:144) ~[spring-data-jpa-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$ExposeRepositoryInvocationInterceptor.invoke(CrudMethodMetadataPostProcessor.java:364) ~[spring-data-jpa-2.1.10.RELEASE.jar:2.1.10.RELEASE]
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at com.sun.proxy.$Proxy95.findAll(Unknown Source) ~[na:na]


@Data
@Entity
@Table(name="trunk_group")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class TrunkGroup implements Serializable {
	
	private static final long serialVersionUID = 1L; 
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name = "_id", updatable = false, nullable = false)
	private Long id;

	@OneToMany(mappedBy="trunkGroup", cascade={CascadeType.PERSIST, CascadeType.REMOVE},orphanRemoval=true)	
	private List<Site> sites;
}


@Data
@Entity
@Table(name="site")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Site implements Serializable {
	
	private static final long serialVersionUID = 1L;
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name = "_id", updatable = false, nullable = false)
	private Long id;
	
	private String siteId;
	
	@ManyToOne
	@JoinColumn(name="trunkGroup_id", nullable=false)
	private TrunkGroup trunkGroup;
			
	@OneToMany(mappedBy = "site", cascade=CascadeType.PERSIST)
	private List<Trunk> trunks;
		
	@OneToMany(mappedBy = "site", cascade=CascadeType.PERSIST)
	private List<DDI> ddis;
	
}

@Data
@Entity
@Table(name="trunk")
@NoArgsConstructor
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trunk implements Serializable {
	
	private static final long serialVersionUID = 1L; 
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name = "_id", updatable = false, nullable = false)
	private Long id;

	@ManyToOne
	@JoinColumn(name="site_id", nullable=false)
	private Site site;
}

Like operation for a LIST type field?

I have a model entity which has a LIST type field, which stores the value in the form a string (comma separated) in sql. I am trying to implement a custom converter which would return all the samples which has a particular list element.

If the values(of a field) in the DB are
"A,B,C",
"A",
"B,C",.
If I provide "A", I want to retrieve both "A,B,C" as well as "A".

This custom converter just returns the sample which has value "A".

RSQLJPASupport.addConverter(List.class, s -> {
            try {
                return Arrays.asList(Arrays.asList(s.split(",")));
            }catch (final IllegalArgumentException ex){
                return null;
            }
        });

Please help.

Datetime type like issue

when I use 1,2,3, They are only date stirng , work well;
when I use 4,5,6, They are have time stirng , doesn't work, no return data;

  1. createTime=bt=("2020-02-01","2020-02-28")

  2. createTime=gt="2020-02-01"

  3. createTime=lt="2020-02-01"

  4. createTime=bt=("2020-02-01 12:00:00","2020-02-28 12:00:00")

  5. createTime=gt="2020-02-01 12:00:00"

  6. createTime=lt="2020-02-01 12:00:00"

code:
@CreatedDate
@TeMPOraL(TemporalType.TIMESTAMP)
private Date createTime;

Support for =ilike= for Enums

Hello I have an issue as stated in subject:

prerequisites

Version 5.0.5.

Enum class:
enum UnitType { SIMPLE, COMPLEX, OTHER }

and an entity MyEntity with enum field:
private UnitType unitType;

query

?filter=unitType=ilike=MP

expected result

MyEntity objects with unitType equal to SIMPLE or COMPLEX (for this particular filter)

actual result

Unfortunately an exception occurs:
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [MP] did not match expected type [...UnitType (n/a)];

summary

Is there any solution to this issue?

Btw. Really nice work with this library Perplexhub :)

Support subqueries

Would it be possible to support the additional operators proposed in:

https://github.com/RutledgePaulV/q-builders

I'm specially interested in the subquery one (=q=). In case it would not be possible, I'd like at least to know how to proceed to extend the current implementation to support it.

QueryDSL Predicate Like issue

for filter (composition=ik= aceclofenac) QueryDSL Predicate findAll working perfectly fine,
But when the string value contains space then it is returning null .

eg: (composition=ik= aceclofenac paracetamol)
Here space present between aceclofenac and paracetamol

RSQLJPASupport not working with @DataJpaTest

I have been trying to run an integrated test with H2, persisting some data to the database and trying to retrieve it with RSQL and RSQLJPASupport specifications. It works well when the query is empty, but breaks as soon as fill the string with some RSQL syntax.

Working scenario

@RunWith(SpringRunner.class)
@ActiveProfiles("dev")
@DataJpaTest
public class RSQLFilterIntegratedTest {

    @Autowired
    private ModelDAO modelDAO;

    @Test
    public void works() {
        Model model = saveNewModel();
        String query = ""; // Query is empty...
        Specification<Model> specification = RSQLJPASupport.toSpecification(query);
        List<Model> models = modelDAO.findAll(specification);
    }
}

Broken scenario

@RunWith(SpringRunner.class)
@ActiveProfiles("dev")
@DataJpaTest
public class RSQLFilterIntegratedTest {

    @Autowired
    private ModelDAO modelDAO;

    @Test
    public void works() {
        Model model = saveNewModel();
        String query = "name==some_name";
        Specification<Model> specification = RSQLJPASupport.toSpecification(query);
        List<Model> models = modelDAO.findAll(specification);
    }
}

It throws the following exception:

org.springframework.dao.InvalidDataAccessApiUsageException: No entity manager bean found in application context; nested exception is java.lang.IllegalStateException: No entity manager bean found in application context

Usually @DataJpaTest injects a TestEntityManager to the application context, so there should be one available for the query. Is it a bug or am I missing something?

Default sorting direction.

Try out code:

@Test
public void testSortDefault() {
  Specification<User> specification = toSort("name");

  List<User> users = userRepository.findAll(specification);
}

Currently user gets

java.lang.ArrayIndexOutOfBoundsException: 1

	at io.github.perplexhub.rsql.SortUtils.sortToJpaOrder(SortUtils.java:35)
	at io.github.perplexhub.rsql.SortUtils.lambda$parseSort$1(SortUtils.java:28)
       ....

It would be reasonable to expect name,asc behavior instead of exception.

Cannot search on more than 3 levels in a @ManyToOne association

Hello, I am getting null pointer exception when I try to do a search using 4 levels like "locate.locateTag.tag.description".

locateTag and tag are complex objects

thank you

java.lang.NullPointerException: null at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.findPropertyPath(RSQLQueryDslPredicateConverter.java:47) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:111) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:26) at cz.jirutka.rsql.parser.ast.ComparisonNode.accept(ComparisonNode.java:70) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.lambda$visit$1(RSQLQueryDslPredicateConverter.java:250) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:250) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:26) at cz.jirutka.rsql.parser.ast.OrNode.accept(OrNode.java:42) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.lambda$visit$0(RSQLQueryDslPredicateConverter.java:243) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:243) at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:26) at cz.jirutka.rsql.parser.ast.AndNode.accept(AndNode.java:42) at io.github.perplexhub.rsql.RSQLQueryDslSupport.toPredicate(RSQLQueryDslSupport.java:37) at io.github.perplexhub.rsql.RSQLQueryDslSupport.toPredicate(RSQLQueryDslSupport.java:29)

rsql-querydsl failed to parse when type is Date

Hi,
When I use rsql-querydsl to parse the string for the query, I found the parser can not work properly
The class I use is in the below. For String Boolean List Integer, it works fine. when comes to Date, If I use createdDate=='2020-03-16 05:00:47.584', It will generate empty query value.

BooleanExpression searchExp = RSQLQueryDslSupport.toPredicate(search, QUser.user);
Hibernate: 
    select
        user0_.uid as uid1_25_,
        user0_.created_by as created_2_25_,
        user0_.created_date as created_3_25_,
        user0_.last_modified_by as last_mod4_25_,
        user0_.last_modified_date as last_mod5_25_,
        user0_.username as username6_25_
    where
        user0_.created_date is null 
    order by
        user0_.created_date desc limit ?

When i use createdDate=lt='2020-03-16 05:00:47.584' , it will throw an exception

java.lang.NullPointerException: null
	at com.querydsl.core.types.ConstantImpl.<init>(ConstantImpl.java:124)
	at com.querydsl.core.types.ConstantImpl.create(ConstantImpl.java:108)
	at com.querydsl.core.types.dsl.ComparableExpression.lt(ComparableExpression.java:250)
	at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:229)
	at io.github.perplexhub.rsql.RSQLQueryDslPredicateConverter.visit(RSQLQueryDslPredicateConverter.java:27)
	at cz.jirutka.rsql.parser.ast.ComparisonNode.accept(ComparisonNode.java:70)
	at io.github.perplexhub.rsql.RSQLQueryDslSupport.toPredicate(RSQLQueryDslSupport.java:37)
	at io.github.perplexhub.rsql.RSQLQueryDslSupport.toPredicate(RSQLQueryDslSupport.java:29)

The entity class I use:

@Data
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler", "fieldHandler"})
public abstract class Audible<U> {
    @CreatedBy
    @Column(nullable = false, updatable = false)
    protected U createdBy;

    @CreatedDate
    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false, updatable = false, columnDefinition = "DATETIME(3)")
    protected Date createdDate;

    @LastModifiedBy
    protected U lastModifiedBy;

    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)
    @Column(columnDefinition = "DATETIME(3)")
    protected Date lastModifiedDate;
}
@Entity
@Data
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Table(name = "sys_user", uniqueConstraints = {@UniqueConstraint(columnNames = {"uid"})})
@JsonView(View.Public.class)
public class User extends Audible<String> implements BaseEntity {
    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Column()
    private String uid;

    @Column(unique = true, nullable = false)
    private String username;

    @JsonSerialize(using = PasswordJsonSerializer.class)
    @Column(nullable = false)
    private String password;

    private String firstName;

    private String lastName;

    private String eMail;

    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(
            name = "sys_user_role_mapping",
            joinColumns = {@JoinColumn(name = "userId", referencedColumnName = "uid")},
            inverseJoinColumns = {@JoinColumn(name = "roleId", referencedColumnName = "uid")}
    )
    @JsonView(View.ExtendedPublic.class)
    private List<Role> roles;

    @Column(nullable = false)
    private Boolean enabled = true;
}

Hint javax.persistence.fetchgraph

Any chance you support Hints like "javax.persistence.fetchgraph"?

e.g:

.setHint("javax.persistence.fetchgraph", entityManager.getEntityGraph("MyEntity.lite"))

Add Custom Predicate

Hi,
Is there a way to hook a custom predicate, for example when i'm looking for a date i want to use a function predicate instead of the equal predicate defined in the RSQLJPAPredicateConverter.

Example:
Imagine i have a query: date==12/05/2020
And I want to use
criteriaBuilder.function( "date_trunc", Date.class, criteriaBuilder.literal("day"), context.getPath() )

Duplicate from table alias when querying different attributes within OneToMany relationship

Hi,
I have this scenario: User has many roles.
RSQL:
name=='g*^';roles.status=='Enabled';roles.type=='Create'
When JPA creates the query it duplicates the ROLE table in FROM clause, which creates an additional unnecessary join and causes data duplication.

I am using spring-data and eclipse-link, I found that hibernate avoids duplication but I have to keep using eclipse-link.

Thanks,

Option to make query do an inner or outer join

Searching on a 'one to many' currently always does an outer join. This is correct when doing an 'or' query but is expensive and unnecessary when doing an 'and' query. I'd like to see if it is possible to either add some logic that determines if an outer or inner is needed or maybe add an option in the query itself.

Filtering on optional one to one attribute does an inner join

( I can give more details later if needed ).
I have a Property entity with an optional bi-directional 1 - 1 relation to an address and my filter does something like:
property.address.addressLine1=='street 1', property.somethingelse==

I would expect to get all properties that have either an address line1 of 'street 1' or where the other property matches some value. However I only get back properties back that have an address of 'street 1'.
The sql that gets generated is doing an inner join on the address table and I think it is because the following code.

RSQLJPAPredicateConverter around line 53 in the 'if' statement:

`if (isAssociationType(mappedProperty, classMetadata)) {
Class<?> associationType = findPropertyType(mappedProperty, classMetadata);
type = associationType;
String previousClass = classMetadata.getJavaType().getName();
classMetadata = getManagedType(associationType);

String keyJoin = root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
log.debug("Create a join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
root = join(keyJoin, root, mappedProperty);`

The join call defaults to an inner join. I think an extra method is needed to determine if the association is required or not and dependent on that outcome do either an inner or left join. (tried to fix that code formatting issue but apparently github doesn't like quotes or double quotes in code snippets ?)

Fix split packages problem

When using rsql-jpa-specification in Java 9 application and higher there is a possibility to configure java modularity. For security reasons starting from Java 9 they disallow split packages. Due to this fact, there is no possibility to gracefully declare rsql-jpa-specification as a module since we cannot require two modules that declare the same package:
module xxx reads package io.github.perplexhub.rsql from both rsql.jpa and rsql.common

I think it is a good idea to get rid of split packages and name them properly.

More info about a similar problem https://stackoverflow.com/a/42358212

Filter nested Collection not work as expected.

When filter an Entity with nested @onetomany Collection, like

{
   "_embedded":{
      "tools":[
         {
            "createdAt":"12.12",
            "parts":[
               {
                  "broken":true
               },
               {
                  "broken":false
               }
            ]
         },
         {
            "createdAt":"12.13",
            "parts":[
               {
                  "broken":true
               },
               {
                  "broken":true
               }
            ]
         }
      ]
   }
}
```
I got not expected output when searching  parts.broken==false

````{json}
{
   "_embedded":{
      "tools":[
         {
            "createdAt":"12.12",
            "parts":[
               {
                  "broken":true
               },
               {
                  "broken":false
               }
            ]
         }
            ]
         }
      ]
   }
}
```
Expected is
````{json}
{
   "_embedded":{
      "tools":[
         {
            "createdAt":"12.12",
            "parts":[
               {
                  "broken":false
               }
            ]
         }
      ]
   }
}
```

How can I solve it.

Order By

Hello perplexhub

this is how I use rsql-jpa-specification

@GetMapping("/suppliers/rsql")
public DataTablesOutput<SupplierLite> findAllByRsql(@Valid DataTablesInput input, @RequestParam(value = "filter", required = true) String filter) {

    return supplierLiteRepository.findAll(input, RSQLJPASupport.toSpecification(filter));
}

I want to be able to order by field

thank you for your time
Arvit

Support json and json columns

Hi,
i use vladmihalcea/hibernate-types to define json and jsonb columns using Postgresql as database.

Here an example:

`

@typedefs({
@typedef(name = "jsonb", typeClass = JsonBinaryType.class),
@typedef(name = "json", typeClass = JsonNodeStringType.class)
})
public class MyEntity {

..
..
@column(name = "segment", columnDefinition = "jsonb")
@type(type = "jsonb")
private Set< String > segment;
...
..
`

The filter is not working ?filter=segment=like=test and i get an error:
"com.fasterxml.jackson.core.JsonParseException: Unexpected character ('%' (code 37)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')\n at [Source: (String)\"%[test]%\"; line: 1, column: 2]; nested exception is java.lang.IllegalArgumentException: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('%' (code 37)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')\n at [Source: (String)\"%[test]%\"; line: 1, column: 2]"

Is there any way to support json/jsonb columns types query ?

thanks

Cannot search on 'null' ids in a manyToOne association

Hi,

First of all, thanks for your great job on that library. It's very useful.

I encountered one unexpected behavior when trying to use the =isnull= operator on a manyToOne association.

I want to find all users with a NULL specialite.

Here are my JPA entities

// UserEntity
@Entity
@Table(name = "domain_user")
public class UserEntity {

{...}

@ManyToOne
@JoinColumn(name = "specialite_id")
private SpecialiteEntity specialite;
}

// SpecialiteEntity
@Entity
@Table(name = "domain_specialite")
public class SpecialiteEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
Long id

{...}
}

My RSQL query looks like that
specialite.id=isnull=

I get no result at all.

I checked the generated SQL request, and there is something wrong

select * from domain_user user0_ inner join domain_specialite specialite1_ on user0_.specialite_id=specialite1_.id where specialite1_.id is null

Because of the where clause on the joined entity, of course there is no specialite with null id in the specialite table

In that particular case, i don't even need any join, the following request would be perfect

select * from domain_user user0_ where user0_.specialite_id is null

To avoid the join, I also tried to not use the 'id' property in the RSQL query

specialite=isnull=

But I got a NullPointerException when you try to resolve the attribute name from the ManyToOne association.

Do you have any workaround to do that ? Maybe i'm doing something wrong with the usage of your library, I hope you could help me :)

Thank you very much,

Cheers,
Cyril

date =gt= or =lt= not working

hello Perplexhub,
I have a java.util.Date field which I want to search and when I use ?filter=date=gt=2018-10-20 which I know in db are some above that date I get no results []

thanks
Arvit

Add support for ManyToMany and ManyToOne in joinHints

Current code only looks at the joinHints parameter if the join type is 1-1 or 1-many and falls back to the default inner join for other types. I would like to have support added for the n-m and many-1 relations as well since those can be optional sometimes.

Unkown properties

Hi, i would like to know how to deal with the properties supplied by frontend app that are not available on filtered entity, currently there is an exception thrown in RSQLJPAPredicateConverter.java:56

if (!hasPropertyName(mappedProperty, classMetadata)) {
throw new IllegalArgumentException("Unknown property: " + mappedProperty + " from entity " + classMetadata.getJavaType().getName());
}

Therefore query is aborted and http error 500 returned.
It would be nice to have a configuration flag that would allow to ignore unknown properties
or some check method that would allow me to return Http code 400 with message which field name is invalid.

divide project in multi modules

I would suggest to divide the project in a multi module project in order to use parts also for other implementations. I would like to use your operators + converters as well for an mongo implementation and maybe some other will follow

  • rsql-spring (api + helpers without javax.persistence dependency )
    • RSQLComplexConverter.java
    • RSQLOperators.java
    • RSQLSimpleConverter.java
  • rsql-jpa-specification
    • RSQLJpaContext.java
    • RSQLVisitorBase.java
    • RSQLJpaPredicateConverter.java
    • RSQLConfig.java (switch logic to auto-configuration)
    • RSQLQueryDslContext.java
    • RSQLQueryDslPredicateConverter.java
    • RSQLSupport.java

What do you think about? I also can help to switch config from @import(io.github.perplexhub.rsql.RSQLConfig.class) to AutoConfiguration...

I've developed some projects like commons-auth or commons-asset that both have got a jpa + mongo persistence implementation. Would be great to use the same base for both...

Unable to query for keys of an associated HashMap of abstract class

Suppose we have an entity that has a @OneToMany association with a HashMap by using @MapKey:

@Entity
@Getter
@Setter
public class ModelA {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "MODEL_A_ID")
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @MapKey(name = "id")
    Map<String, ModelD> keyToModelD = new HashMap<>();

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "MODEL_B_ID", referencedColumnName = "id")
    private final ModelB modelB = new ModelB();
}

@Entity
@Getter
@Setter
public class ModelD {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

I have tried writing a rsql query in two ways... None of these worked.

  1. "keyToModelD==SOME_KEY" - "Unknown property: key from entity" exception.
  2. "keyToModelD.key==SOME_KEY" - NullPointerException on RSQLJPAPredicateConverter.java:92

How can we achieve such a query?

Any example on how to perform cross entity search?

Say I have an abstract entity with is extended by 2-3 entities, how do I perform search in this case?
abstract class A {
I tried doing this jpaSpecificationExecutor(Class<? extends BaseClass>) during dao implementation. But search works only with base class fields

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.