GithubHelp home page GithubHelp logo

vineey / archelix-rsql Goto Github PK

View Code? Open in Web Editor NEW
39.0 11.0 9.0 552 KB

This library brings the convenience of SQL declarative nature to restful APIs in the form of RSQL but without the danger of sql injection by using a typesafe mapping of allowed field paths defined via integration with querydsl library. Like sql, it supports clauses such as select, filter, pagination and sorting that can easily be represented in http request parameters. It primarily supports JPA model and MongoDB query via Querydsl.

License: MIT License

Java 100.00%
rql rsql querydsl spring-data-jpa spring-data spring-data-mongodb rest

archelix-rsql's Introduction

RSQL-QueryDSL

Author : John Michael Vincent S. Rustia

Build Status License Coverage Status Codacy code quality Maven Central Gitter

This library brings the convenience of SQL declarative nature to restful APIs in the form of RSQL but without the danger of sql injection by using a typesafe mapping of allowed field paths defined via integration with querydsl library. Like sql, it supports clauses such as select, filter, pagination and sorting that can easily be represented in http request parameters.

It primarily supports JPA model and MongoDB query via Querydsl. This is a small project but at its heart is dedicated to maintain highly cohesive and modular components. Contributions and suggestions are very much welcome and appreciated!

QueryDSL API Modules

Rql specification used by this library is defined by this reference and rsql-parser.
Please see for more information about rql expressions.

  • select - converts rql select expression into its querydsl projection equivalent

  • filter - uses rsql-parser library ast to convert filter string into its querydsl predicate equivalent

  • page - converts rql limit expression into its querydsl pagination equivalent

  • sort - converts rql sort expression into its querydsl sorting equivalent

  • spring - provides integration with spring data such as pageable

2.0.0.RELEASE Notes

#13 Ported rsql-querydsl v1.0.0.RELEASE code to v2.0.0 branch to be compatible with querydsl latest version v4.x.x.
1.0.0.RELEASE and 2.0.0 will be simultaneous releases.

Contributions:

  • Thanks @natros for pointing me on this direction, hence a simultaneous 2.0.0.RELEASE

  • Thanks Wallace Wadge for your effort in delivering this task, you are awesome!

1.0.0.RELEASE Notes

Completes filter conversion to querydsl predicate. Completes sort and page conversion for rsql querydsl
Completes select conversion to querydsl projections.
Full support on Mongodb projection, filter, sort and page conversion.

NEXT MILESTONES

2.1.0 and 1.1.0 Milestones

  • Add support for embeddable or nested Querydsl Paths

  • Add support for JPA Association Paths (OneToMany, ManyToOne)

Rsql-JOOQ Milestones

  • Support for rsql conversion to JOOQ Library

MAVEN

Bundled Rsql Querydsl Modules

You can get all rsql-querydsl modules via this dependency,

For Querydsl 3.x.x, use 1.0.0.RELEASE
For Querydsl 4.x.x, use 2.0.0.RELEASE

<dependency>
    <groupId>com.github.vineey</groupId>
    <artifactId>rsql-querydsl-all</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>
dependencies {
    compile 'com.github.vineey:rsql-querydsl-all:2.0.0.RELEASE'
  }

Selective Rsql Querydsl Modules

or you can specify which module you only need by changing the artifactId by any of the ff:

  • rsql-querydsl-select

  • rsql-querydsl-filter

  • rsql-querydsl-sort

  • rsql-querydsl-page

such as

dependencies {
    compile 'com.github.vineey:rsql-querydsl-filter:2.0.0.RELEASE'
  }

To integrate with Spring Data Pageable, include this dependency,

dependencies {
    compile 'com.github.vineey:rsql-querydsl-spring:2.0.0.RELEASE'
  }

Minimum JDK Required

  • Java 8 (will try to support Java 7 in the next release by externalizing Java 8 DateTime converters)

Querydsl Integration Dependencies Required

  • com.mysema.querydsl:querydsl-core:3.x.x or prior for rsql-querydsl 1.x.x.RELEASE

  • com.querydsl:querydsl-core:4.x.x or latest for rsql-querydsl 2.x.x.RELEASE

Spring Integration Dependencies Required

  • org.springframework.data:spring-data-commons:1.x.x or latest

API DOCS

Querydsl Filter (rsql-querydsl-filter)

Note: AOTM, this only supports first level fields of the entity or document. But will support embeddable pojo and other kinds of nested querydsl path.

Sample operators

     equals is ==
           e.g. field == 'value'
     not equals is !=
            e.g. field != 'value'
     like is =*
            startsWith e.g.  field == 'value*'
            endsWith   e.g.  field == '*value'
            contains   e.g.  field == '*value*'
DefaultFilterParser filterParser = new DefaultFilterParser();

String rqlFilter = "employee.name == 'John'";

Map<String, Path> pathHashMap = ImmutableMap.<String, Path>builder()
                .put("employee.name", QEmployee.employee.name)
                .put("employee.age", QEmployee.employee.age)
                .put("employee.bday", QEmployee.employee.birthDate)
                .build();

Predicate predicate = filterParser.parse(rsqlFilter, withBuilderAndParam(new QuerydslFilterBuilder(), new QuerydslFilterParam()
                                                                             .setMapping(pathHashMap)));

  //or a shorter version

Predicate predicate = filterParser.parse(rsqlFilter, withMapping(pathHashMap));

Querydsl Select Conversion (rsql-querydsl-select)

Note: AOTM, this only supports first level fields of the entity or document. But will support embeddable pojo and other kinds of nested querydsl path.

//e.g. select(field1, field2,...)
String rqlSelectExpression = "select(contact.company, contact.name, contact.age)";
DefaultSelectParser selectParser = new DefaultSelectParser();
Map<String, Path> mappings = ImmutableMap.<String, Path>builder()
        .put("contact.age", QContactDocument.contactDocument.age)
        .put("contact.name", QContactDocument.contactDocument.name)
        .put("contact.bday", QContactDocument.contactDocument.bday)
        .put("contact.company", QContactDocument.contactDocument.company)
        .build();

Expression projection = selectParser.parse(rqlSelectExpression, QuerydslSelectContext.withMapping(QContactDocument.contactDocument, mappings));

Querydsl Sort Conversion (rsql-querydsl-sort)

Note: AOTM, this only supports first level fields of the entity or document. But will support embeddable pojo and other kinds of nested querydsl path.

//ascending is +, descending is -
//e.g. sort(+field1, -field2,...)
String sortExpression = "sort(+employeeNumber)";

DefaultSortParser sortParser = new DefaultSortParser();

Map<String, Path> mappings = ImmutableMap.<String, Path>builder()
        .put("employeeNumber", QEmployee.employee.employeeNumber)
        .build();

OrderSpecifierList orderSpecifierList = sortParser.parse(sortExpression, QuerydslSortContext.withMapping(mappings));

List<OrderSpecifier> orderSpecifiers = orderSpecifierList.getOrders();

Querydsl Page Conversion (rsql-querydsl-page)

//limit(<offset>, <size>)
String rqlPage = "limit(10, 5)";

DefaultPageParser defaultPageParser = new DefaultPageParser();

QueryModifiers querydslPage = defaultPageParser.parse(rqlPage, withDefault());

or a simplified version

QuerydslPageParser querydslPageParser = new QuerydslPageParser();

QueryModifiers querydslPage = querydslPageParser.parse(rqlPage);

Bundled All Querydsl Modules (rsql-querydsl-all)

String rqlSelect = "select(contact.name, contact.age)";
String rqlFilter = "(contact.age =='1' and contact.name == 'A*') or (contact.age > '1'  and contact.bday == '2015-05-05')";
String limit = "limit(0, 10)";
String sort = "sort(+contact.name)";

RqlInput rqlInput = new RqlInput()
        .setSelect(rqlSelect)
        .setFilter(rqlFilter)
        .setLimit(limit)
        .setSort(sort);

Map<String , Path> pathMapping = ImmutableMap.<String, Path>builder()
        .put("contact.name", QContactDocument.contactDocument.name)
        .put("contact.age", QContactDocument.contactDocument.age)
        .put("contact.bday", QContactDocument.contactDocument.bday)
        .build();

QuerydslMappingResult querydslMappingResult = querydslRqlParser.parse(rqlInput, new QuerydslMappingParam().setRootPath(QContactDocument.contactDocument).setPathMapping(pathMapping));

Expression selectExpression = querydslMappingResult.getProjection();
Predicate predicate = querydslMappingResult.getPredicate();

QueryModifiers querydslPage = querydslMappingResult.getPage();

List<OrderSpecifier> orderSpecifiers = querydslMappingResult.getOrderSpecifiers();

Integration of Querydsl to Spring Data Pageable

Pageable pageable = SpringUtil.toPageable(orderSpecifiers, querydslPage);

You can now use Expression, Predicate, QueryModifiers, OrderSpecifier or Pageable
in the Querydsl API, or in JPAQuery,
or in the Spring Data JPA/Mongo Repository.

A MORE APPROPRIATE WIKI

To be follow!!!

archelix-rsql's People

Contributors

vineey avatar wwadge 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

archelix-rsql's Issues

Converters

Implements converters from rsql-parser to Querydsl sets of Path types such as DatePath, DateTimePath, StringPath, EnumPath, NumberPath, BooleanPath, CollectionPath, TimePath,etc.

Issue with EnumPathConverter

It doesn't convert to enum in case of "in" and "out" queries.

And exception as a result:

Caused by: java.lang.IllegalArgumentException: Parameter value [ACTIVE] did not match expected type [com.***.Status (n/a)]
at org.hibernate.jpa.spi.BaseQueryImpl.validateBinding(BaseQueryImpl.java:874) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.internal.QueryImpl.access$000(QueryImpl.java:80) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.internal.QueryImpl$ParameterRegistrationImpl.bindValue(QueryImpl.java:248) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.internal.QueryImpl$JpaPositionalParameterRegistrationImpl.bindValue(QueryImpl.java:337) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.spi.BaseQueryImpl.setParameter(BaseQueryImpl.java:674) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.spi.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:198) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.spi.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:49) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at com.mysema.query.jpa.impl.JPAUtil.setConstants(JPAUtil.java:55) ~[querydsl-jpa-3.6.5.jar:na]
at com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:130) ~[querydsl-jpa-3.6.5.jar:na]
at com.mysema.query.jpa.impl.AbstractJPAQuery.count(AbstractJPAQuery.java:81) ~[querydsl-jpa-3.6.5.jar:na]

*Proposed solution: *

public BooleanExpression evaluate(EnumPath path, ComparisonNode comparisonNode) {
        ComparisonOperator comparisonOperator = comparisonNode.getOperator();
        List<String> args = comparisonNode.getArguments();

        Function<String, Enum<?>> toEnumFunction = (String arg) -> {
            Enum enumArg = EnumUtils.getEnum(path.getType(), arg.toUpperCase());
            if (enumArg == null && !NULL.equalsIgnoreCase(arg)) {
                throw new IllegalArgumentException("Nonexistent enum value: " + arg);
            }
            return enumArg;
        };

        List<Enum> enumArgs = args.stream().map(toEnumFunction).collect(Collectors.toList());
        String firstArg = args.get(0);
        Enum firstEnumArg = enumArgs.get(0);

        if (EQUAL.equals(comparisonOperator)) {
            return NULL.equalsIgnoreCase(firstArg) ? path.isNull() : path.eq(firstEnumArg);
        } else if (NOT_EQUAL.equals(comparisonOperator)) {
            return NULL.equalsIgnoreCase(firstArg) ? path.isNotNull() : path.ne(firstEnumArg);
        } else if (IN.equals(comparisonOperator)) {
            return path.in(enumArgs);
        } else if (NOT_IN.equals(comparisonOperator)) {
            return path.notIn(enumArgs);
        }
        throw new UnsupportedRqlOperatorException(comparisonNode, path.getClass());
    }

How to query by relationship?

Is it possible to query by relationship (using spring-data-mongodb)?

I've an Item Model and an Item belongs to a Category. I would like to query the items by category.id=={id}.

I already added these path mappings but the results are always empty.

.put("category", QProduct.item.category)
.put("category.id", QProduct.item.category.id)

RHS path support (Ad Hoc JOIN)?

Hi,

Is it possible to have an expression like employee.company_id == company.id ?

i.e. having a path on both sides of the expression?

Is there a way to go without mappings.

I always need to map type -> QEntity.entity.type, type.value -> QEntity.entity.type.value, etc.
Is there a way to not explicitly specify all mappings as I want this to work for all class properties and paths and I have many entities. I can easily create a method that generates from a string a class path through reflection but I'll need the mapping object to be a closure that takes (String path, Class qclass ) and return the Path object instead of a static Map.

License file is missing

This looks like a nice library. Could you please add a license file or statement indicating what the licensing of this code is. For example, the RSQL project on which this is based is MIT License.

Thanks.

Support ISO8660 date formats.

First of all, great job!

The spec in http://doc.apsstandard.org/2.1/spec/rql/ mandate dates to be in ISO UTC format. This also is the date format on the JSON spec and the format returned by JS Date.toJSON. This means the DateUtil class should not fail on strings like 2016-07-17T02:59:59.999Z.

The thing, though is how that it converts to a LocalDateTime. I'm not sure if dropping the time zone info (the Z in the string) is always the correct solution since some masochists developers might use something different than UTC for their timezone.

Use a generic DSL API

Using a generic DSL API, this shields users from internal implementations and any future refactoring.
Also refactor querydsl modules. split into major modules such as mongodb and jpa implementations.

Custom path converter support

Hello
It seems like the PathConverterContext con't be extened to supprot other path types, like UUID for example. My filter looks like this : uuidField=='CED17E16-78AF-47F6-8ACB-CF5CEEBBF8DB' where uuidField field is defined as private UUID uuidField.
The QuerydslRsqlVisitor::visit method fails with NullPointerException because PathConverterContext.getOperator(path) return null for UUID type.
Is there any workaround to overcome this ?
Thanks

NPE during simple filter parsing

I haven't dug into the issue, but when parsing a simple filter principal == 035489f4-d602-46c5-b568-39d1f852b565 I get an NPE:

Caused by: java.lang.NullPointerException
	at com.github.vineey.rql.querydsl.filter.QuerydslRsqlVisitor.visit(QuerydslRsqlVisitor.java:80) ~[rsql-querydsl-filter-2.0.0.RELEASE.jar:?]
	at com.github.vineey.rql.querydsl.filter.QuerydslRsqlVisitor.visit(QuerydslRsqlVisitor.java:37) ~[rsql-querydsl-filter-2.0.0.RELEASE.jar:?]
	at cz.jirutka.rsql.parser.ast.ComparisonNode.accept(ComparisonNode.java:70) ~[rsql-parser-2.1.0.jar:2.1.0]
	at com.github.vineey.rql.querydsl.filter.QuerydslFilterBuilder.visit(QuerydslFilterBuilder.java:34) ~[rsql-querydsl-filter-2.0.0.RELEASE.jar:?]
	at com.github.vineey.rql.querydsl.filter.QuerydslFilterBuilder.visit(QuerydslFilterBuilder.java:31) ~[rsql-querydsl-filter-2.0.0.RELEASE.jar:?]
	at com.github.vineey.rql.filter.parser.DefaultFilterParser.parse(DefaultFilterParser.java:42) ~[rsql-api-filter-2.0.0.RELEASE.jar:?]

QueryDSL SQL 4.1.0

Hello,

You have here a very interesting project that I would like to use. My question is, does it work with last version of QueryDSL SQL 4.1.0?

Thank you.

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.