Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a clauseTemplate for Numeric fieldType to skip the escaping of … #514

Merged
merged 1 commit into from
Oct 8, 2024

Conversation

foogaro
Copy link
Contributor

@foogaro foogaro commented Oct 7, 2024

…the characters.

Using Redis OM Spring 0.9.4, I've implemented the following repository:

@Repository
public interface BeersRepository extends CrudRepository<Beer, String> {

    List<Beer> searchBeerByName(String name);
    Iterable<Beer> findByAbv(String abv);
    Iterable<Beer> findByAbvGreaterThanEqual(String abvGTE);
    Iterable<Beer> findByAbvLessThanEqual(String abvLTE);
    Iterable<Beer> findByAbvBetween(String abvGT, String abvLT);
    Iterable<Beer> findByIbu(String ibu);
    Iterable<Beer> findByIbuGreaterThanEqual(String ibuGTE);
    Iterable<Beer> findByIbuLessThanEqual(String ibuLTE);
    Iterable<Beer> findByIbuBetween(String ibuGT, String ibuLT);

}

for the following entity:

@RedisHash("beer")
public class Beer {

    @Id
    private String id;
    @NumericIndexed(sortable = true)
    private String abv; //Alcohol by volume (ABV).
    private Integer available_id;
    private String beer_variation_id;
    private String create_date;
    private String description;
    private String food_parings;
    private Integer glassware_id;
    @NumericIndexed(sortable = true)
    private String ibu; //Bitterness
    private String is_organic;
    private String is_retired;
    @Searchable
    private String name;
    private String name_display;
    private String original_gravity;
    private String serving_temperature;
    private String serving_temperature_display;
    private Integer srm_id;
    private String status;
    private String status_display;
    private Integer style_id;
    private String update_date;
    private Integer year;

}

The search is made available through a REST API as follows:

    @GetMapping("/by-abv/{abvGT}/{abvLT}")
    public ResponseEntity findByAbvBetween(@PathVariable("abvGT") String abvGT, @PathVariable("abvLT") String abvLT) {
        Iterable<Beer> beers = service.findByAbvBetween(abvGT, abvLT);
        if (beers != null) {
            return ResponseEntity.ok(beers);
        } else {
            return ResponseEntity.notFound().build();
        }
    }

Issue

Using the curl command I receive the following error:

curl -X GET http://localhost:8080/beers/by-abv/21.9/22.1
{"timestamp":"2024-10-07T13:52:37.806+00:00","status":500,"error":"Internal Server Error","path":"/beers/by-abv/21.9/22.1"}%

Using the Redis Insight Profiler I see the following command being invoked on Redis:

15:52:37.788 [0 192.168.65.1:26960] "FT.SEARCH" "com.foogaro.panel.model.BeerIdx" "@abv:[21\\.9 22\\.1]" "LIMIT" "0" "10000" "DIALECT" "1"

And at application level I get the following error in the console:

2024-10-07T15:52:37.793+02:00 ERROR 62679 --- [beers] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessApiUsageException: Syntax error at offset 6 near 21\.9] with root cause

redis.clients.jedis.exceptions.JedisDataException: Syntax error at offset 6 near 21\.9
    at redis.clients.jedis.Protocol.processError(Protocol.java:105) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.Protocol.process(Protocol.java:162) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.Protocol.read(Protocol.java:221) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:350) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.Connection.getOne(Connection.java:332) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.Connection.executeCommand(Connection.java:137) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.executors.DefaultCommandExecutor.executeCommand(DefaultCommandExecutor.java:24) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.UnifiedJedis.executeCommand(UnifiedJedis.java:250) ~[jedis-5.1.0.jar:na]
    at redis.clients.jedis.UnifiedJedis.ftSearch(UnifiedJedis.java:3719) ~[jedis-5.1.0.jar:na]
    at com.redis.om.spring.ops.search.SearchOperationsImpl.search(SearchOperationsImpl.java:46) ~[redis-om-spring-0.9.4.jar:na]
    at com.redis.om.spring.repository.query.RedisEnhancedQuery.executeQuery(RedisEnhancedQuery.java:489) ~[redis-om-spring-0.9.4.jar:na]
    at com.redis.om.spring.repository.query.RedisEnhancedQuery.execute(RedisEnhancedQuery.java:387) ~[redis-om-spring-0.9.4.jar:na]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.3.2.jar:3.3.2]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.3.2.jar:3.3.2]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164) ~[spring-data-commons-3.3.2.jar:3.3.2]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143) ~[spring-data-commons-3.3.2.jar:3.3.2]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.1.11.jar:6.1.11]
    at jdk.proxy2/jdk.proxy2.$Proxy86.findByAbvBetween(Unknown Source) ~[na:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.1.11.jar:6.1.11]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.11.jar:6.1.11]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.1.11.jar:6.1.11]
    at jdk.proxy2/jdk.proxy2.$Proxy86.findByAbvBetween(Unknown Source) ~[na:na]
    at com.foogaro.panel.service.BeersService.findByAbvBetween(BeersService.java:42) ~[classes/:na]
    at com.foogaro.panel.controller.BeersController.findByAbvBetween(BeersController.java:54) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255) ~[spring-web-6.1.11.jar:6.1.11]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188) ~[spring-web-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.11.jar:6.1.11]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.11.jar:6.1.11]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.11.jar:6.1.11]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.11.jar:6.1.11]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.11.jar:6.1.11]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.11.jar:6.1.11]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.11.jar:6.1.11]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.26.jar:10.1.26]
    at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

Expected

Expected behaviour is to generate proper Redis Search query without escaping the decimal dot separator, as follows:

FT.SEARCH com.foogaro.panel.model.BeerIdx "@abv:[21.9 22.1]" LIMIT 0 10000 DIALECT 1

Fix

In the com.redis.om.spring.repository.query.clause.QueryClause enum, in the public String prepareQuery(String field, Object... params) method, add a condition for FieldType.NUMERIC to do not execute the QueryUtils.escape(ObjectUtils.asString(param, converter)) check on the param.

Result

Query executed properly and result given.

Redis Insight Profiler:

15:56:00.969 [0 192.168.65.1:55214] "FT.SEARCH" "com.foogaro.panel.model.BeerIdx" "@abv:[21.9 22.1]" "LIMIT" "0" "10000" "DIALECT" "1"

curl:

curl -X GET http://localhost:8080/beers/by-abv/21.9/22.1
[{"id":"wSkM9L","abv":"22","available_id":1,"beer_variation_id":null,"create_date":"2012-10-21 20:34:15","description":"Dark Strong Ale","food_parings":null,"glassware_id":4,"ibu":"48","is_organic":"N","is_retired":"N","name":"OMG","name_display":"OMG","original_gravity":"1.185","serving_temperature":"cold","serving_temperature_display":"Cold - (4-7C/39-45F)","srm_id":16,"status":"verified","status_display":"Verified","style_id":34,"update_date":"2019-06-28 15:13:30","year":null}]%

Copy link
Contributor

@bsbodden bsbodden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@bsbodden bsbodden closed this Oct 8, 2024
@bsbodden bsbodden reopened this Oct 8, 2024
@bsbodden bsbodden merged commit 4a8287d into redis:main Oct 8, 2024
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants