dengliming/redis-modules-java

RediSearch - NoContent option not working

alxgrk opened this issue · 3 comments

Hi there,

first, thanks for creating & maintaining this library.

I am using the redisearch module and tried to search for documents with NOCONTENT option set.
The following would be a sample request (in Kotlin):

myIndex.searchAsync("@foo:{bar}", SearchOptions().noContent())

However, for a result set of 1 document I get the following exception:

java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
	at java.base/java.util.Objects.checkIndex(Objects.java:372)
	at java.base/java.util.ArrayList.get(ArrayList.java:459)
	at io.github.dengliming.redismodule.redisearch.protocol.decoder.SearchResultDecoder.decode(SearchResultDecoder.java:58)
	at io.github.dengliming.redismodule.redisearch.protocol.decoder.SearchResultDecoder.decode(SearchResultDecoder.java:31)
	at org.redisson.client.protocol.decoder.ListMultiDecoder2.decode(ListMultiDecoder2.java:47)
	at org.redisson.client.handler.CommandDecoder.decodeList(CommandDecoder.java:436)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:392)
	at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:198)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:137)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:113)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:510)
	at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:279)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:722)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:658)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:584)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:834)

It seems that the logic at SearchResultDecoder for whether noContent was set or not is incorrect. At least, for the case where total == parts.get(0) == 1, parts.size() + 1 would equal 3, as parts.get(1) is the id of the document.

@Override
    public SearchResult decode(List<Object> parts, State state) {
        Long total = (Long) parts.get(0);
        int documentSize = withScores ? 3 : 2;
        boolean noContent = total == parts.size() + 1;

        List<Document> documents = new ArrayList<>(total.intValue());
        // Checks the document size. DocumentSize equals to 2 means only key and parts. DocumentSize equals to 3 means
        // key, score and parts. Created separated IFs to avoid checking this logic each  document. Also  changed  the
        // step size to reduce numbers of interactions
        if (documentSize == 2) {
            //Only key and parts
            for (int i = 1; i < parts.size(); i += documentSize) {
                if (noContent) {
                    documents.add(new Document((String) parts.get(i), 1.0d, null));
                } else {
                    documents.add(new Document((String) parts.get(i), 1.0d, (Map<String, Object>) parts.get(i + 1)));
                }
            }
        } else {
        // ...

I'm not sure though how to fix this, any ideas? Just removing the + 1 part would surely do the trick, but does is still work with other result / document sizes?

Best regards,
Alex

@alxgrk Hey thanks for reporting this issue. I think this is a bug. Would you be interested in fixing it?
Here's a quick solution.
boolean noContent = total.longValue() == parts.size() - 1;

Fixed by cfe6e70

@dengliming thank you for fixing it so quickly. Whenever you find the time to create a new patch version, I would be happy to test the change.