yahoo / elide

Elide is a Java library that lets you stand up a GraphQL/JSON-API web service with minimal effort.
https://elide.io
Other
1k stars 228 forks source link

RSQL =ini= cannot handle kotlin enums #3109

Open manosbatsis opened 11 months ago

manosbatsis commented 11 months ago

Consider a (kotlin) enum:

enum class Symbol { FOO, BAR, BAZ }

and it's use in some entity:

@Entity
@Include(rootLevel = true, name = "myentity")
class MyEntity(
    // ...
    @Enumerated(EnumType.STRING)
    @Column(length = 3, nullable = false, updatable = false)
    val symbol: Symbol,
    // ...
)

Current Behavior

This works:

/api/reserve?filter[myentity]=symbol==FOO

but this:

/api/reserve?filter[myentity]=symbol=ini=FOO*

thows the exception bellow. Seems the query visitor makes a string-type assumption because of the operator:

java.lang.ClassCastException: class example.Symbol cannot be cast to class java.lang.String (example.Symbol is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
at org.hibernate.type.descriptor.java.StringJavaType.unwrap(StringJavaType.java:27) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.type.descriptor.jdbc.VarcharJdbcType$1.doBind(VarcharJdbcType.java:108) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.type.descriptor.jdbc.BasicBinder.bind(BasicBinder.java:61) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:118) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:108) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.bindParameters(DeferredResultSetAccess.java:199) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:228) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:163) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.advanceNext(JdbcValuesResultSetImpl.java:254) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.processNext(JdbcValuesResultSetImpl.java:134) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.results.jdbc.internal.AbstractJdbcValues.next(AbstractJdbcValues.java:19) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.sql.results.internal.RowProcessingStateStandardImpl.next(RowProcessingStateStandardImpl.java:66) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:51) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at org.hibernate.query.internal.ScrollableResultsIterator.hasNext(ScrollableResultsIterator.java:33) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
at java.base/java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1855) ~[na:na]
at java.base/java.util.Spliterators$1Adapter.hasNext(Spliterators.java:681) ~[na:na]
at com.yahoo.elide.datastores.jpql.porting.ScrollableIteratorBase.<init>(ScrollableIteratorBase.java:39) ~[elide-datastore-hibernate-7.0.0-pr6.jar:na]
at com.yahoo.elide.datastores.jpa.ScrollableIterator.<init>(ScrollableIterator.java:19) ~[elide-datastore-jpa-7.0.0-pr6.jar:na]
at com.yahoo.elide.datastores.jpa.porting.QueryWrapper.scroll(QueryWrapper.java:62) ~[elide-datastore-jpa-7.0.0-pr6.jar:na]
at com.yahoo.elide.datastores.jpql.JPQLTransaction.lambda$loadObjects$1(JPQLTransaction.java:130) ~[elide-datastore-hibernate-7.0.0-pr6.jar:na]
at com.yahoo.elide.core.utils.TimedFunction.get(TimedFunction.java:33) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.datastores.jpql.JPQLTransaction.loadObjects(JPQLTransaction.java:131) ~[elide-datastore-hibernate-7.0.0-pr6.jar:na]
at com.yahoo.elide.datastores.multiplex.MultiplexTransaction.loadObjects(MultiplexTransaction.java:68) ~[elide-datastore-multiplex-7.0.0-pr6.jar:na]
at com.yahoo.elide.core.datastore.inmemory.InMemoryStoreTransaction.lambda$loadObjects$2(InMemoryStoreTransaction.java:117) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.core.datastore.inmemory.InMemoryStoreTransaction.fetchData(InMemoryStoreTransaction.java:262) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.core.datastore.inmemory.InMemoryStoreTransaction.loadObjects(InMemoryStoreTransaction.java:123) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.core.PersistentResource.loadRecords(PersistentResource.java:374) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.jsonapi.parser.state.CollectionTerminalState.getResourceCollection(CollectionTerminalState.java:148) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.jsonapi.parser.state.CollectionTerminalState.handleGet(CollectionTerminalState.java:77) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.jsonapi.parser.state.StateContext.handleGet(StateContext.java:108) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.jsonapi.parser.GetVisitor.visitQuery(GetVisitor.java:31) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.jsonapi.parser.GetVisitor.visitQuery(GetVisitor.java:18) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.generated.parsers.CoreParser$QueryContext.accept(CoreParser.java:582) ~[elide-core-7.0.0-pr6.jar:na]
at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visitChildren(AbstractParseTreeVisitor.java:46) ~[antlr4-runtime-4.13.0.jar:4.13.0]
at com.yahoo.elide.generated.parsers.CoreBaseVisitor.visitStart(CoreBaseVisitor.java:21) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.jsonapi.parser.BaseVisitor.visitStart(BaseVisitor.java:47) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.jsonapi.parser.BaseVisitor.visitStart(BaseVisitor.java:33) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.generated.parsers.CoreParser$StartContext.accept(CoreParser.java:118) ~[elide-core-7.0.0-pr6.jar:na]
at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18) ~[antlr4-runtime-4.13.0.jar:4.13.0]
at com.yahoo.elide.Elide.visit(Elide.java:572) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.Elide.lambda$get$1(Elide.java:270) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.Elide.handleRequest(Elide.java:596) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.Elide.get(Elide.java:263) ~[elide-core-7.0.0-pr6.jar:na]
at com.yahoo.elide.spring.controllers.JsonApiController$1.call(JsonApiController.java:85) ~[elide-spring-boot-autoconfigure-7.0.0-pr6.jar:na]
at com.yahoo.elide.spring.controllers.JsonApiController$1.call(JsonApiController.java:82) ~[elide-spring-boot-autoconfigure-7.0.0-pr6.jar:na]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$4(WebAsyncManager.java:341) ~[spring-web-6.0.13.jar:6.0.13]
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Your Environment

aklish commented 10 months ago

The asterisk is treated by the FIQL parser as a wildcard (and the operator becomes a prefix query). For in-memory evaluation of the filter, Elide attempts to coerce any argument compared against a wildcard to a String.

The other place the wildcard is evaluated is in the JPQL generation for filtering directly against the database:

        GLOBAL_OPERATOR_GENERATORS.put(PREFIX, new CaseAwareJPQLGenerator(
                "%s LIKE CONCAT(%s, '%%')",
                CaseAwareJPQLGenerator.Case.NONE,
                CaseAwareJPQLGenerator.ArgumentCount.ONE,
                true)
        );

That looks like where the problematic String conversion is happening. You can override the JPQL generation for any model field to work around this:

https://elide.io/pages/guide/v7/16-performance.html#jpql-fragment-override