From fe7d92c3a693faaabca3ddbb6d30477241079aef Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Sun, 14 Nov 2021 11:58:48 +0600 Subject: [PATCH] RediSearch commands implementation (continued) and tests --- pom.xml | 5 + .../redis/clients/jedis/BuilderFactory.java | 34 + .../redis/clients/jedis/CommandArguments.java | 2 +- .../jedis/RedisClusterCommandObjects.java | 109 +- .../clients/jedis/RedisCommandObjects.java | 70 +- .../redis/clients/jedis/UnifiedJedis.java | 47 +- .../jedis/commands/MiscellaneousCommands.java | 2 + .../redis/clients/jedis/search/Query.java | 54 +- .../jedis/search/RediSearchCommands.java | 10 + .../clients/jedis/search/RediSearchUtil.java | 36 + .../clients/jedis/search/SearchProtocol.java | 7 +- .../clients/jedis/search/aggr/Reducers.java | 122 ++ ....java => RedisModuleCommandsTestBase.java} | 13 +- .../jedis/tests/modules/json/JsonTest.java | 8 +- .../search/AggregationBuilderTest.java | 244 +++ .../tests/modules/search/CreateTest.java | 70 + .../tests/modules/search/DocumentTest.java | 45 + .../tests/modules/search/JsonSearchTest.java | 225 +++ .../jedis/tests/modules/search/QueryTest.java | 156 ++ .../tests/modules/search/SchemaTest.java | 34 + .../tests/modules/search/SearchTest.java | 1381 +++++++++++++++++ 21 files changed, 2610 insertions(+), 64 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/search/RediSearchUtil.java create mode 100644 src/main/java/redis/clients/jedis/search/aggr/Reducers.java rename src/test/java/redis/clients/jedis/tests/modules/{ModuleCommandsTestBase.java => RedisModuleCommandsTestBase.java} (85%) create mode 100644 src/test/java/redis/clients/jedis/tests/modules/search/AggregationBuilderTest.java create mode 100644 src/test/java/redis/clients/jedis/tests/modules/search/CreateTest.java create mode 100644 src/test/java/redis/clients/jedis/tests/modules/search/DocumentTest.java create mode 100644 src/test/java/redis/clients/jedis/tests/modules/search/JsonSearchTest.java create mode 100644 src/test/java/redis/clients/jedis/tests/modules/search/QueryTest.java create mode 100644 src/test/java/redis/clients/jedis/tests/modules/search/SchemaTest.java create mode 100644 src/test/java/redis/clients/jedis/tests/modules/search/SearchTest.java diff --git a/pom.xml b/pom.xml index 45114e86f4..33f273dea4 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,11 @@ gson 2.8.8 + + org.json + json + 20210307 + junit diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index 791504014c..25281c8ac7 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -61,6 +61,19 @@ public String toString() { } }; + public static final Builder> ENCODED_OBJECT_MAP_FROM_PLAIN_LIST = new Builder>() { + @Override + public Map build(Object data) { + final List list = (List) data; + final Map map = new HashMap<>(list.size() / 2, 1); + final Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + map.put(STRING.build(iterator.next()), ENCODED_OBJECT.build(iterator.next())); + } + return map; + } + }; + public static final Builder LONG = new Builder() { @Override public Long build(Object data) { @@ -1222,6 +1235,27 @@ public LCSMatchResult build(Object data) { } }; + public static final Builder> STRING_MAP_FROM_PAIR_ARRAY = new Builder>() { + @Override + @SuppressWarnings("unchecked") + public Map build(Object data) { + final List list = (List) data; + final Map map = new HashMap<>(list.size()); + for (Object object : list) { + final List flat = (List) object; + map.put(SafeEncoder.encode(flat.get(0)), flat.get(1) != null ? SafeEncoder.encode(flat.get(1)) : null); + } + + return map; + } + + @Override + public String toString() { + return "Map"; + } + + }; + public static final Builder> JSON_TYPE = new Builder>() { @Override public Class build(Object data) { diff --git a/src/main/java/redis/clients/jedis/CommandArguments.java b/src/main/java/redis/clients/jedis/CommandArguments.java index ec692df405..8b125cd2b5 100644 --- a/src/main/java/redis/clients/jedis/CommandArguments.java +++ b/src/main/java/redis/clients/jedis/CommandArguments.java @@ -52,7 +52,7 @@ public CommandArguments addObjects(Object... args) { return this; } - public CommandArguments addObjects(Collection args) { + public CommandArguments addObjects(Collection args) { for (Object arg : args) { add(arg); } diff --git a/src/main/java/redis/clients/jedis/RedisClusterCommandObjects.java b/src/main/java/redis/clients/jedis/RedisClusterCommandObjects.java index 5f2f4592bf..23f4e129cc 100644 --- a/src/main/java/redis/clients/jedis/RedisClusterCommandObjects.java +++ b/src/main/java/redis/clients/jedis/RedisClusterCommandObjects.java @@ -2,18 +2,21 @@ import static redis.clients.jedis.Protocol.Command.KEYS; import static redis.clients.jedis.Protocol.Command.SCAN; -import static redis.clients.jedis.Protocol.Command.WAIT; import static redis.clients.jedis.Protocol.Keyword.TYPE; +import java.util.List; +import java.util.Map; import java.util.Set; + import redis.clients.jedis.commands.ProtocolCommand; import redis.clients.jedis.params.ScanParams; import redis.clients.jedis.resps.ScanResult; import redis.clients.jedis.search.IndexOptions; import redis.clients.jedis.search.Query; import redis.clients.jedis.search.Schema; -import redis.clients.jedis.search.SearchProtocol; import redis.clients.jedis.search.SearchResult; +import redis.clients.jedis.search.aggr.AggregationBuilder; +import redis.clients.jedis.search.aggr.AggregationResult; import redis.clients.jedis.util.JedisClusterHashTag; public class RedisClusterCommandObjects extends RedisCommandObjects { @@ -25,6 +28,11 @@ protected ClusterCommandArguments commandArguments(ProtocolCommand command) { private static final String CLUSTER_UNSUPPORTED_MESSAGE = "Not supported in cluster mode."; + @Override + public CommandObject dbSize() { + throw new UnsupportedOperationException(CLUSTER_UNSUPPORTED_MESSAGE); + } + private static final String KEYS_PATTERN_MESSAGE = "Cluster mode only supports KEYS command" + " with pattern containing hash-tag ( curly-brackets enclosed string )"; @@ -105,23 +113,106 @@ private CommandObject processSearchCommand(String indexName, CommandObjec return command; } - private CommandObject processSearchCommand(byte[] indexName, CommandObject command) { + @Override + public final CommandObject ftCreate(String indexName, IndexOptions indexOptions, Schema schema) { + return processSearchCommand(indexName, super.ftCreate(indexName, indexOptions, schema)); + } + + @Override + public final CommandObject ftAlter(String indexName, Schema schema) { + return processSearchCommand(indexName, super.ftAlter(indexName, schema)); + } + + @Override + public final CommandObject ftSearch(String indexName, Query query) { + return processSearchCommand(indexName, super.ftSearch(indexName, query)); + } + + @Override + public final CommandObject ftSearch(byte[] indexName, Query query) { + CommandObject command = super.ftSearch(indexName, query); if (searchLite) command.getArguments().processKey(indexName); return command; } @Override - public CommandObject ftCreate(String indexName, IndexOptions indexOptions, Schema schema) { - return processSearchCommand(indexName, super.ftCreate(indexName, indexOptions, schema)); + public CommandObject ftExplain(String indexName, Query query) { + return processSearchCommand(indexName, super.ftExplain(indexName, query)); } @Override - public CommandObject ftSearch(String indexName, Query query) { - return processSearchCommand(indexName, super.ftSearch(indexName, query)); + public CommandObject> ftExplainCLI(String indexName, Query query) { + return processSearchCommand(indexName, super.ftExplainCLI(indexName, query)); } @Override - public CommandObject ftSearch(byte[] indexName, Query query) { - return processSearchCommand(indexName, super.ftSearch(indexName, query)); + public CommandObject ftAggregate(String indexName, AggregationBuilder aggr) { + return processSearchCommand(indexName, super.ftAggregate(indexName, aggr)); + } + + @Override + public CommandObject ftCursorRead(String indexName, long cursorId, int count) { + return processSearchCommand(indexName, super.ftCursorRead(indexName, cursorId, count)); + } + + @Override + public CommandObject ftCursorDel(String indexName, long cursorId) { + return processSearchCommand(indexName, super.ftCursorDel(indexName, cursorId)); + } + + @Override + public CommandObject ftDropIndex(String indexName) { + return processSearchCommand(indexName, super.ftDropIndex(indexName)); + } + + @Override + public CommandObject ftDropIndexDD(String indexName) { + return processSearchCommand(indexName, super.ftDropIndexDD(indexName)); + } + + @Override + public CommandObject ftSynUpdate(String indexName, String synonymGroupId, String... terms) { + return processSearchCommand(indexName, super.ftSynUpdate(indexName, synonymGroupId, terms)); + } + + @Override + public CommandObject>> ftSynDump(String indexName) { + return processSearchCommand(indexName, super.ftSynDump(indexName)); + } + + @Override + public CommandObject> ftInfo(String indexName) { + return processSearchCommand(indexName, super.ftInfo(indexName)); + } + + @Override + public CommandObject ftAliasAdd(String aliasName, String indexName) { + CommandObject command = super.ftAliasAdd(aliasName, indexName); + if (searchLite) command.getArguments().processKey(aliasName).processKey(indexName); + return command; + } + + @Override + public CommandObject ftAliasUpdate(String aliasName, String indexName) { + CommandObject command = super.ftAliasUpdate(aliasName, indexName); + if (searchLite) command.getArguments().processKey(aliasName).processKey(indexName); + return command; + } + + @Override + public CommandObject ftAliasDel(String aliasName) { + CommandObject command = super.ftAliasDel(aliasName); + if (searchLite) command.getArguments().processKey(aliasName); + return command; + } + + @Override + public CommandObject> ftConfigGet(String indexName, String option) { + return processSearchCommand(indexName, super.ftConfigGet(indexName, option)); + } + + @Override + public CommandObject ftConfigSet(String indexName, String option, String value) { + return processSearchCommand(indexName, super.ftConfigSet(indexName, option, value)); } } diff --git a/src/main/java/redis/clients/jedis/RedisCommandObjects.java b/src/main/java/redis/clients/jedis/RedisCommandObjects.java index 84f36c4f98..f13fb20c7a 100644 --- a/src/main/java/redis/clients/jedis/RedisCommandObjects.java +++ b/src/main/java/redis/clients/jedis/RedisCommandObjects.java @@ -258,6 +258,10 @@ public final CommandObject renamenx(byte[] oldkey, byte[] newkey) { return new CommandObject<>(commandArguments(RENAMENX).key(oldkey).key(newkey), BuilderFactory.LONG); } + public CommandObject dbSize() { + return new CommandObject<>(commandArguments(DBSIZE), BuilderFactory.LONG); + } + public CommandObject> keys(String pattern) { CommandArguments args = commandArguments(Command.KEYS).key(pattern); return new CommandObject<>(args, BuilderFactory.STRING_SET); @@ -2445,7 +2449,7 @@ public CommandObject ftCreate(String indexName, IndexOptions indexOption return new CommandObject<>(args, BuilderFactory.STRING); } - public final CommandObject ftAlter(String indexName, Schema schema) { + public CommandObject ftAlter(String indexName, Schema schema) { CommandArguments args = commandArguments(SearchCommand.ALTER).add(indexName) .add(SearchKeyword.SCHEMA).add(SearchKeyword.ADD); schema.fields.forEach(field -> field.addParams(args)); @@ -2462,28 +2466,82 @@ public CommandObject ftSearch(byte[] indexName, Query query) { new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), query.getWithPayloads(), false)); } - public final CommandObject ftAggregate(String indexName, AggregationBuilder aggr) { + public CommandObject ftExplain(String indexName, Query query) { + return new CommandObject<>(commandArguments(SearchCommand.EXPLAIN).add(indexName).addParams(query), BuilderFactory.STRING); + } + + public CommandObject> ftExplainCLI(String indexName, Query query) { + return new CommandObject<>(commandArguments(SearchCommand.EXPLAINCLI).add(indexName).addParams(query), BuilderFactory.STRING_LIST); + } + + public CommandObject ftAggregate(String indexName, AggregationBuilder aggr) { return new CommandObject<>(commandArguments(SearchCommand.AGGREGATE).add(indexName).addObjects(aggr.getArgs()), !aggr.isWithCursor() ? BuilderFactory.SEARCH_AGGREGATION_RESULT : BuilderFactory.SEARCH_AGGREGATION_RESULT_WITH_CURSOR); } - public final CommandObject ftDropIndex(String indexName) { + public CommandObject ftCursorRead(String indexName, long cursorId, int count) { + return new CommandObject<>(commandArguments(SearchCommand.CURSOR).add(SearchKeyword.READ) + .add(indexName).add(cursorId).add(count), BuilderFactory.SEARCH_AGGREGATION_RESULT_WITH_CURSOR); + } + + public CommandObject ftCursorDel(String indexName, long cursorId) { + return new CommandObject<>(commandArguments(SearchCommand.CURSOR).add(SearchKeyword.DEL) + .add(indexName).add(cursorId), BuilderFactory.STRING); + } + + public CommandObject ftDropIndex(String indexName) { return new CommandObject<>(commandArguments(SearchCommand.DROPINDEX).add(indexName), BuilderFactory.STRING); } - public final CommandObject ftDropIndexDD(String indexName) { + public CommandObject ftDropIndexDD(String indexName) { return new CommandObject<>(commandArguments(SearchCommand.DROPINDEX).add(indexName).add(SearchKeyword.DD), BuilderFactory.STRING); } - public final CommandObject ftSynUpdate(String indexName, String synonymGroupId, String... terms) { + public CommandObject ftSynUpdate(String indexName, long synonymGroupId, String... terms) { return new CommandObject<>(commandArguments(SearchCommand.SYNUPDATE).add(indexName) .add(synonymGroupId).addObjects((Object[]) terms), BuilderFactory.STRING); } - public final CommandObject>> ftSynDump(String indexName) { + public CommandObject ftSynUpdate(String indexName, String synonymGroupId, String... terms) { + return new CommandObject<>(commandArguments(SearchCommand.SYNUPDATE).add(indexName) + .add(synonymGroupId).addObjects((Object[]) terms), BuilderFactory.STRING); + } + + public CommandObject>> ftSynDump(String indexName) { return new CommandObject<>(commandArguments(SearchCommand.SYNDUMP).add(indexName), BuilderFactory.SEARCH_SYNONYM_GROUPS); } + public CommandObject> ftInfo(String indexName) { + return new CommandObject<>(commandArguments(SearchCommand.INFO).add(indexName), BuilderFactory.ENCODED_OBJECT_MAP_FROM_PLAIN_LIST); + } + + public CommandObject ftAliasAdd(String aliasName, String indexName) { + return new CommandObject<>(commandArguments(SearchCommand.ALIASADD).add(aliasName).add(indexName), BuilderFactory.STRING); + } + + public CommandObject ftAliasUpdate(String aliasName, String indexName) { + return new CommandObject<>(commandArguments(SearchCommand.ALIASUPDATE).add(aliasName).add(indexName), BuilderFactory.STRING); + } + + public CommandObject ftAliasDel(String aliasName) { + return new CommandObject<>(commandArguments(SearchCommand.ALIASDEL).add(aliasName), BuilderFactory.STRING); + } + + public final CommandObject> ftConfigGet(String option) { + return new CommandObject<>(commandArguments(SearchCommand.CONFIG).add(SearchKeyword.GET).add(option), BuilderFactory.STRING_MAP_FROM_PAIR_ARRAY); + } + + public CommandObject> ftConfigGet(String indexName, String option) { + return ftConfigGet(option); + } + + public final CommandObject ftConfigSet(String option, String value) { + return new CommandObject<>(commandArguments(SearchCommand.CONFIG).add(SearchKeyword.SET).add(option).add(value), BuilderFactory.STRING); + } + + public CommandObject ftConfigSet(String indexName, String option, String value) { + return ftConfigSet(option, value); + } // RediSearch commands // RedisJSON commands diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index d694d50d3d..f3848f84ef 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -94,7 +94,7 @@ public void close() { IOUtils.closeQuietly(this.executor); } - protected final T executeCommand(CommandObject commandObject) { + public final T executeCommand(CommandObject commandObject) { return executor.executeCommand(commandObject); } @@ -379,6 +379,11 @@ public long renamenx(byte[] oldkey, byte[] newkey) { return executeCommand(commandObjects.renamenx(oldkey, newkey)); } + @Override + public long dbSize() { + return executeCommand(commandObjects.dbSize()); + } + @Override public Set keys(String pattern) { return executeCommand(commandObjects.keys(pattern)); @@ -2803,12 +2808,12 @@ public SearchResult ftSearch(byte[] indexName, Query query) { @Override public String ftExplain(String indexName, Query query) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftExplain(indexName, query)); } @Override public List ftExplainCLI(String indexName, Query query) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftExplainCLI(indexName, query)); } @Override @@ -2816,6 +2821,16 @@ public AggregationResult ftAggregate(String indexName, AggregationBuilder aggr) return executeCommand(commandObjects.ftAggregate(indexName, aggr)); } + @Override + public AggregationResult ftCursorRead(String indexName, long cursorId, int count) { + return executeCommand(commandObjects.ftCursorRead(indexName, cursorId, count)); + } + + @Override + public String ftCursorDel(String indexName, long cursorId) { + return executeCommand(commandObjects.ftCursorDel(indexName, cursorId)); + } + @Override public String ftDropIndex(String indexName) { return executeCommand(commandObjects.ftDropIndex(indexName)); @@ -2826,6 +2841,11 @@ public String ftDropIndexDD(String indexName) { return executeCommand(commandObjects.ftDropIndexDD(indexName)); } + @Override + public String ftSynUpdate(String indexName, long synonymGroupId, String... terms) { + return executeCommand(commandObjects.ftSynUpdate(indexName, synonymGroupId, terms)); + } + @Override public String ftSynUpdate(String indexName, String synonymGroupId, String... terms) { return executeCommand(commandObjects.ftSynUpdate(indexName, synonymGroupId, terms)); @@ -2838,34 +2858,43 @@ public Map> ftSynDump(String indexName) { @Override public Map ftInfo(String indexName) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftInfo(indexName)); } @Override public String ftAliasAdd(String aliasName, String indexName) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftAliasAdd(aliasName, indexName)); } @Override public String ftAliasUpdate(String aliasName, String indexName) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftAliasUpdate(aliasName, indexName)); } @Override public String ftAliasDel(String aliasName) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftAliasDel(aliasName)); } @Override public Map ftConfigGet(String option) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftConfigGet(option)); + } + + @Override + public Map ftConfigGet(String indexName, String option) { + return executeCommand(commandObjects.ftConfigGet(indexName, option)); } @Override public String ftConfigSet(String option, String value) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + return executeCommand(commandObjects.ftConfigSet(option, value)); } + @Override + public String ftConfigSet(String indexName, String option, String value) { + return executeCommand(commandObjects.ftConfigSet(indexName, option, value)); + } // RediSearch commands // RedisJSON commands diff --git a/src/main/java/redis/clients/jedis/commands/MiscellaneousCommands.java b/src/main/java/redis/clients/jedis/commands/MiscellaneousCommands.java index 5904bc9db8..1bae2f6a81 100644 --- a/src/main/java/redis/clients/jedis/commands/MiscellaneousCommands.java +++ b/src/main/java/redis/clients/jedis/commands/MiscellaneousCommands.java @@ -5,6 +5,8 @@ public interface MiscellaneousCommands { + long dbSize(); + long publish(final String channel, final String message); LCSMatchResult strAlgoLCSStrings(final String strA, final String strB, final StrAlgoLCSParams params); diff --git a/src/main/java/redis/clients/jedis/search/Query.java b/src/main/java/redis/clients/jedis/search/Query.java index 21aaf568bb..e66ec2db1d 100644 --- a/src/main/java/redis/clients/jedis/search/Query.java +++ b/src/main/java/redis/clients/jedis/search/Query.java @@ -121,40 +121,40 @@ public HighlightTags(String open, String close) { /** * The query's filter list. We only support AND operation on all those filters */ - protected final List _filters = new LinkedList<>(); + private final List _filters = new LinkedList<>(); /** * The textual part of the query */ - protected final String _queryString; + private final String _queryString; /** * The sorting parameters */ - protected final Paging _paging = new Paging(0, 10); - - protected boolean _verbatim = false; - protected boolean _noContent = false; - protected boolean _noStopwords = false; - protected boolean _withScores = false; - protected boolean _withPayloads = false; - protected String _language = null; - protected String[] _fields = null; - protected String[] _keys = null; - protected String[] _returnFields = null; + private final Paging _paging = new Paging(0, 10); + + private boolean _verbatim = false; + private boolean _noContent = false; + private boolean _noStopwords = false; + private boolean _withScores = false; + private boolean _withPayloads = false; + private String _language = null; + private String[] _fields = null; + private String[] _keys = null; + private String[] _returnFields = null; private FieldName[] returnFieldNames = null; - protected String[] highlightFields = null; - protected String[] summarizeFields = null; - protected String[] highlightTags = null; - protected String summarizeSeparator = null; - protected int summarizeNumFragments = -1; - protected int summarizeFragmentLen = -1; - protected byte[] _payload = null; - protected String _sortBy = null; - protected boolean _sortAsc = true; - protected boolean wantsHighlight = false; - protected boolean wantsSummarize = false; - protected String _scorer = null; + private String[] highlightFields = null; + private String[] summarizeFields = null; + private String[] highlightTags = null; + private String summarizeSeparator = null; + private int summarizeNumFragments = -1; + private int summarizeFragmentLen = -1; + private byte[] _payload = null; + private String _sortBy = null; + private boolean _sortAsc = true; + private boolean wantsHighlight = false; + private boolean wantsSummarize = false; + private String _scorer = null; public Query() { this("*"); @@ -280,10 +280,10 @@ public void addParams(CommandArguments args) { } } else if (returnFieldNames != null && returnFieldNames.length > 0) { args.add(SearchKeyword.RETURN.getRaw()); - final int returnCountIndex = args.size(); +// final int returnCountIndex = args.size(); DelayedRawable returnCountObject = new DelayedRawable(); // args.add(null); // holding a place for setting the total count later. - args.add(returnCountIndex); // holding a place for setting the total count later. + args.add(returnCountObject); // holding a place for setting the total count later. int returnCount = 0; for (FieldName fn : returnFieldNames) { returnCount += fn.addCommandArguments(args); diff --git a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java index 45a3c77c12..2d0c3521a9 100644 --- a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java +++ b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java @@ -25,10 +25,16 @@ default String ftAlter(String indexName, Schema.Field... fields) { AggregationResult ftAggregate(String indexName, AggregationBuilder aggr); + AggregationResult ftCursorRead(String indexName, long cursorId, int count); + + String ftCursorDel(String indexName, long cursorId); + String ftDropIndex(String indexName); String ftDropIndexDD(String indexName); + String ftSynUpdate(String indexName, long synonymGroupId, String... terms); + String ftSynUpdate(String indexName, String synonymGroupId, String... terms); Map> ftSynDump(String indexName); @@ -43,5 +49,9 @@ default String ftAlter(String indexName, Schema.Field... fields) { Map ftConfigGet(String option); + Map ftConfigGet(String indexName, String option); + String ftConfigSet(String option, String value); + + String ftConfigSet(String indexName, String option, String value); } diff --git a/src/main/java/redis/clients/jedis/search/RediSearchUtil.java b/src/main/java/redis/clients/jedis/search/RediSearchUtil.java new file mode 100644 index 0000000000..1200a5f253 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/RediSearchUtil.java @@ -0,0 +1,36 @@ +package redis.clients.jedis.search; + +import java.util.HashMap; +import java.util.Map; +import redis.clients.jedis.util.SafeEncoder; + +public class RediSearchUtil { + + public static Map toStringMap(Map input) { + Map output = new HashMap<>(input.size()); + for (Map.Entry entry : input.entrySet()) { + String key = entry.getKey(); + Object obj = entry.getValue(); + if (key == null || obj == null) { + throw new NullPointerException("A null argument cannot be sent to Redis."); + } + String str; + if (obj instanceof byte[]) { + str = SafeEncoder.encode((byte[]) obj); + } else if (obj instanceof redis.clients.jedis.GeoCoordinate) { + redis.clients.jedis.GeoCoordinate geo = (redis.clients.jedis.GeoCoordinate) obj; + str = geo.getLongitude() + "," + geo.getLatitude(); + } else if (obj instanceof String) { + str = (String) obj; + } else { + str = obj.toString(); + } + output.put(key, str); + } + return output; + } + + private RediSearchUtil() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/src/main/java/redis/clients/jedis/search/SearchProtocol.java b/src/main/java/redis/clients/jedis/search/SearchProtocol.java index 0b03c78856..6424b3f07b 100644 --- a/src/main/java/redis/clients/jedis/search/SearchProtocol.java +++ b/src/main/java/redis/clients/jedis/search/SearchProtocol.java @@ -13,6 +13,7 @@ public enum SearchCommand implements ProtocolCommand { INFO("FT.INFO"), SEARCH("FT.SEARCH"), EXPLAIN("FT.EXPLAIN"), + EXPLAINCLI("FT.EXPLAINCLI"), AGGREGATE("FT.AGGREGATE"), CURSOR("FT.CURSOR"), CONFIG("FT.CONFIG"), @@ -43,9 +44,9 @@ public enum SearchKeyword implements Rawable { SCHEMA, VERBATIM, NOCONTENT, NOSTOPWORDS, WITHSCORES, WITHPAYLOADS, LANGUAGE, INFIELDS, SORTBY, ASC, DESC, PAYLOAD, LIMIT, HIGHLIGHT, FIELDS, TAGS, SUMMARIZE, FRAGS, LEN, SEPARATOR, INKEYS, - RETURN, /*NOSAVE, PARTIAL, REPLACE,*/ FILTER, GEOFILTER, INCR, MAX, FUZZY, DD, DELETE, READ, COUNT, - ADD, TEMPORARY, STOPWORDS, NOFREQS, NOFIELDS, NOOFFSETS, /*IF,*/ SET, GET, ON, ASYNC, PREFIX, - LANGUAGE_FIELD, SCORE_FIELD, SCORE, PAYLOAD_FIELD, SCORER; + RETURN, /*NOSAVE, PARTIAL, REPLACE,*/ FILTER, GEOFILTER, INCR, MAX, FUZZY, DD, /*DELETE,*/ DEL, + READ, COUNT, ADD, TEMPORARY, STOPWORDS, NOFREQS, NOFIELDS, NOOFFSETS, /*IF,*/ SET, GET, ON, + ASYNC, PREFIX, LANGUAGE_FIELD, SCORE_FIELD, SCORE, PAYLOAD_FIELD, SCORER; private final byte[] raw; diff --git a/src/main/java/redis/clients/jedis/search/aggr/Reducers.java b/src/main/java/redis/clients/jedis/search/aggr/Reducers.java new file mode 100644 index 0000000000..4ac4d4ccfc --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/Reducers.java @@ -0,0 +1,122 @@ +package redis.clients.jedis.search.aggr; + +import java.util.List; + +/** + * Created by mnunberg on 2/22/18. + */ +public class Reducers { + + public static Reducer count() { + return new Reducer() { + @Override + public String getName() { + return "COUNT"; + } + }; + } + + private static Reducer singleFieldReducer(String name, String field) { + return new Reducer(field) { + @Override + public String getName() { + return name; + } + }; + } + + public static Reducer count_distinct(String field) { + return singleFieldReducer("COUNT_DISTINCT", field); + } + + public static Reducer count_distinctish(String field) { + return singleFieldReducer("COUNT_DISTINCTISH", field); + } + + public static Reducer sum(String field) { + return singleFieldReducer("SUM", field); + } + + public static Reducer min(String field) { + return singleFieldReducer("MIN", field); + } + + public static Reducer max(String field) { + return singleFieldReducer("MAX", field); + } + + public static Reducer avg(String field) { + return singleFieldReducer("AVG", field); + } + + public static Reducer stddev(String field) { + return singleFieldReducer("STDDEV", field); + } + + public static Reducer quantile(String field, double percentile) { + return new Reducer(field) { + @Override + public String getName() { + return "QUANTILE"; + } + + @Override + protected List getOwnArgs() { + List args = super.getOwnArgs(); + args.add(Double.toString(percentile)); + return args; + } + }; + } + + /** + * REDUCE FIRST_VALUE {nargs} {property} [BY {property} [ASC|DESC]] + * + * @param field + * @param sortBy + * @return Reducer + */ + public static Reducer first_value(String field, SortedField sortBy) { + return new Reducer(field) { + @Override + public String getName() { + return "FIRST_VALUE"; + } + + @Override + protected List getOwnArgs() { + List args = super.getOwnArgs(); + if (sortBy != null) { + args.add("BY"); + args.add(sortBy.getField()); + args.add(sortBy.getOrder()); + } + return args; + } + }; + } + + public static Reducer first_value(String field) { + return first_value(field, null); + } + + public static Reducer to_list(String field) { + return singleFieldReducer("TOLIST", field); + } + + public static Reducer random_sample(String field, int size) { + return new Reducer(field) { + @Override + public String getName() { + return "RANDOM_SAMPLE"; + } + + @Override + protected List getOwnArgs() { + List args = super.getOwnArgs(); + args.add(Integer.toString(size)); + return args; + } + }; + } +} diff --git a/src/test/java/redis/clients/jedis/tests/modules/ModuleCommandsTestBase.java b/src/test/java/redis/clients/jedis/tests/modules/RedisModuleCommandsTestBase.java similarity index 85% rename from src/test/java/redis/clients/jedis/tests/modules/ModuleCommandsTestBase.java rename to src/test/java/redis/clients/jedis/tests/modules/RedisModuleCommandsTestBase.java index 3519085ba0..68c49edac0 100644 --- a/src/test/java/redis/clients/jedis/tests/modules/ModuleCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/tests/modules/RedisModuleCommandsTestBase.java @@ -14,19 +14,18 @@ import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.providers.PooledJedisConnectionProvider; -public abstract class ModuleCommandsTestBase { +public abstract class RedisModuleCommandsTestBase { protected static final HostAndPort hnp = new HostAndPort(Protocol.DEFAULT_HOST, 6479); private static final PooledJedisConnectionProvider provider = new PooledJedisConnectionProvider(hnp); protected UnifiedJedis client; - public ModuleCommandsTestBase() { + public RedisModuleCommandsTestBase() { super(); } - @BeforeClass - public static void prepare() throws Exception { + public static void prepare() { try (Connection connection = new Connection(hnp)) { assumeTrue("No Redis running on 6479 port. Ignoring modules tests.", connection.ping()); } catch (JedisConnectionException jce) { @@ -35,7 +34,7 @@ public static void prepare() throws Exception { } @Before - public void setUp() throws Exception { + public void setUp() { try (Jedis jedis = createJedis()) { jedis.flushAll(); } @@ -45,6 +44,10 @@ public void setUp() throws Exception { // @After // public void tearDown() throws Exception { // client.close(); +// } +// +// public static void tearDown() { +// client.close(); // } protected static Jedis createJedis() { diff --git a/src/test/java/redis/clients/jedis/tests/modules/json/JsonTest.java b/src/test/java/redis/clients/jedis/tests/modules/json/JsonTest.java index e2a14a8f3b..8e058de8ad 100644 --- a/src/test/java/redis/clients/jedis/tests/modules/json/JsonTest.java +++ b/src/test/java/redis/clients/jedis/tests/modules/json/JsonTest.java @@ -47,13 +47,13 @@ import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.json.JsonSetParams; import redis.clients.jedis.json.Path; -import redis.clients.jedis.tests.modules.ModuleCommandsTestBase; +import redis.clients.jedis.tests.modules.RedisModuleCommandsTestBase; -public class JsonTest extends ModuleCommandsTestBase { +public class JsonTest extends RedisModuleCommandsTestBase { @BeforeClass - public static void prepare() throws Exception { - ModuleCommandsTestBase.prepare(); + public static void prepare() { + RedisModuleCommandsTestBase.prepare(); } /* A simple class that represents an object in real life */ diff --git a/src/test/java/redis/clients/jedis/tests/modules/search/AggregationBuilderTest.java b/src/test/java/redis/clients/jedis/tests/modules/search/AggregationBuilderTest.java new file mode 100644 index 0000000000..b0fbf3d425 --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/modules/search/AggregationBuilderTest.java @@ -0,0 +1,244 @@ +package redis.clients.jedis.tests.modules.search; + +import redis.clients.jedis.exceptions.JedisDataException; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import redis.clients.jedis.search.Document; +import redis.clients.jedis.search.FieldName; +import redis.clients.jedis.search.IndexOptions; +import redis.clients.jedis.search.Schema; +import redis.clients.jedis.search.aggr.AggregationBuilder; +import redis.clients.jedis.search.aggr.AggregationResult; +import redis.clients.jedis.search.aggr.Reducers; +import redis.clients.jedis.search.aggr.Row; +import redis.clients.jedis.search.aggr.SortedField; +import redis.clients.jedis.tests.modules.RedisModuleCommandsTestBase; + +public class AggregationBuilderTest extends RedisModuleCommandsTestBase { + + private static final String index = "aggbindex"; + + @BeforeClass + public static void prepare() { + RedisModuleCommandsTestBase.prepare(); + } + + @AfterClass + public static void tearDown() { +// RedisModuleCommandsTestBase.tearDown(); + } + + private void addDocument(Document doc) { + String key = doc.getId(); + Map map = new LinkedHashMap<>(); + doc.getProperties().forEach(entry -> map.put(entry.getKey(), String.valueOf(entry.getValue()))); + client.hset(key, map); + } + + private void addDocument(String key, Map objMap) { + Map strMap = new HashMap<>(); + objMap.entrySet().forEach(entry -> strMap.put(entry.getKey(), String.valueOf(entry.getValue()))); + client.hset(key, strMap); + } + + @Test + public void testAggregations() { + Schema sc = new Schema(); + sc.addSortableTextField("name", 1.0); + sc.addSortableNumericField("count"); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); +// client.addDocument(new Document("data1").set("name", "abc").set("count", 10)); +// client.addDocument(new Document("data2").set("name", "def").set("count", 5)); +// client.addDocument(new Document("data3").set("name", "def").set("count", 25)); + addDocument(new Document("data1").set("name", "abc").set("count", 10)); + addDocument(new Document("data2").set("name", "def").set("count", 5)); + addDocument(new Document("data3").set("name", "def").set("count", 25)); + + AggregationBuilder r = new AggregationBuilder() + .groupBy("@name", Reducers.sum("@count").as("sum")) + .sortBy(10, SortedField.desc("@sum")); + + // actual search + AggregationResult res = client.ftAggregate(index, r); + assertEquals(2, res.totalResults); + + Row r1 = res.getRow(0); + assertNotNull(r1); + assertEquals("def", r1.getString("name")); + assertEquals(30, r1.getLong("sum")); + assertEquals(30., r1.getDouble("sum"), 0); + + assertEquals(0L, r1.getLong("nosuchcol")); + assertEquals(0.0, r1.getDouble("nosuchcol"), 0); + assertEquals("", r1.getString("nosuchcol")); + + Row r2 = res.getRow(1); + assertNotNull(r2); + assertEquals("abc", r2.getString("name")); + assertEquals(10, r2.getLong("sum")); + } + + @Test + public void testApplyAndFilterAggregations() { + Schema sc = new Schema(); + sc.addSortableTextField("name", 1.0); + sc.addSortableNumericField("subj1"); + sc.addSortableNumericField("subj2"); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); +// client.addDocument(new Document("data1").set("name", "abc").set("subj1", 20).set("subj2", 70)); +// client.addDocument(new Document("data2").set("name", "def").set("subj1", 60).set("subj2", 40)); +// client.addDocument(new Document("data3").set("name", "ghi").set("subj1", 50).set("subj2", 80)); +// client.addDocument(new Document("data4").set("name", "abc").set("subj1", 30).set("subj2", 20)); +// client.addDocument(new Document("data5").set("name", "def").set("subj1", 65).set("subj2", 45)); +// client.addDocument(new Document("data6").set("name", "ghi").set("subj1", 70).set("subj2", 70)); + addDocument(new Document("data1").set("name", "abc").set("subj1", 20).set("subj2", 70)); + addDocument(new Document("data2").set("name", "def").set("subj1", 60).set("subj2", 40)); + addDocument(new Document("data3").set("name", "ghi").set("subj1", 50).set("subj2", 80)); + addDocument(new Document("data4").set("name", "abc").set("subj1", 30).set("subj2", 20)); + addDocument(new Document("data5").set("name", "def").set("subj1", 65).set("subj2", 45)); + addDocument(new Document("data6").set("name", "ghi").set("subj1", 70).set("subj2", 70)); + + AggregationBuilder r = new AggregationBuilder().apply("(@subj1+@subj2)/2", "attemptavg") + .groupBy("@name", Reducers.avg("@attemptavg").as("avgscore")) + .filter("@avgscore>=50") + .sortBy(10, SortedField.asc("@name")); + + // actual search + AggregationResult res = client.ftAggregate(index, r); + assertEquals(3, res.totalResults); + + Row r1 = res.getRow(0); + assertNotNull(r1); + assertEquals("def", r1.getString("name")); + assertEquals(52.5, r1.getDouble("avgscore"), 0); + + Row r2 = res.getRow(1); + assertNotNull(r2); + assertEquals("ghi", r2.getString("name")); + assertEquals(67.5, r2.getDouble("avgscore"), 0); + } + + @Test + public void testLoadAsAggregations() { + Schema sc = new Schema(); + sc.addSortableTextField("name", 1.0); + sc.addSortableNumericField("subj1"); + sc.addSortableNumericField("subj2"); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); +// client.addDocument(new Document("data1").set("name", "abc").set("subj1", 20).set("subj2", 70)); +// client.addDocument(new Document("data2").set("name", "def").set("subj1", 60).set("subj2", 40)); + addDocument(new Document("data1").set("name", "abc").set("subj1", 20).set("subj2", 70)); + addDocument(new Document("data2").set("name", "def").set("subj1", 60).set("subj2", 40)); + + AggregationBuilder builder = new AggregationBuilder() + .load(FieldName.of("@subj1").as("a"), FieldName.of("@subj2").as("b")) + .apply("(@a+@b)/2", "avg").sortByDesc("@avg"); + + AggregationResult result = client.ftAggregate(index, builder); + assertEquals(50.0, result.getRow(0).getDouble("avg"), 0d); + assertEquals(45.0, result.getRow(1).getDouble("avg"), 0d); + } + + @Test + public void testCursor() throws InterruptedException { + Schema sc = new Schema(); + sc.addSortableTextField("name", 1.0); + sc.addSortableNumericField("count"); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); +// client.addDocument(new Document("data1").set("name", "abc").set("count", 10)); +// client.addDocument(new Document("data2").set("name", "def").set("count", 5)); +// client.addDocument(new Document("data3").set("name", "def").set("count", 25)); + addDocument(new Document("data1").set("name", "abc").set("count", 10)); + addDocument(new Document("data2").set("name", "def").set("count", 5)); + addDocument(new Document("data3").set("name", "def").set("count", 25)); + + AggregationBuilder r = new AggregationBuilder() + .groupBy("@name", Reducers.sum("@count").as("sum")) + .sortBy(10, SortedField.desc("@sum")) + .cursor(1, 3000); + + // actual search + AggregationResult res = client.ftAggregate(index, r); + assertEquals(2, res.totalResults); + + Row row = res.getRow(0); + assertNotNull(row); + assertEquals("def", row.getString("name")); + assertEquals(30, row.getLong("sum")); + assertEquals(30., row.getDouble("sum"), 0); + + assertEquals(0L, row.getLong("nosuchcol")); + assertEquals(0.0, row.getDouble("nosuchcol"), 0); + assertEquals("", row.getString("nosuchcol")); + + res = client.ftCursorRead(index, res.getCursorId(), 1); + Row row2 = res.getRow(0); + assertNotNull(row2); + assertEquals("abc", row2.getString("name")); + assertEquals(10, row2.getLong("sum")); + + assertEquals("OK", client.ftCursorDel(index, res.getCursorId())); + + try { + client.ftCursorRead(index, res.getCursorId(), 1); + fail(); + } catch (JedisDataException e) { + } + + AggregationBuilder r2 = new AggregationBuilder() + .groupBy("@name", Reducers.sum("@count").as("sum")) + .sortBy(10, SortedField.desc("@sum")) + .cursor(1, 1000); + + Thread.sleep(1000); + + try { + client.ftCursorRead(index, res.getCursorId(), 1); + fail(); + } catch (JedisDataException e) { + } + } + + @Test + public void testWrongAggregation() throws InterruptedException { + Schema sc = new Schema() + .addTextField("title", 5.0) + .addTextField("body", 1.0) + .addTextField("state", 1.0) + .addNumericField("price"); + + client.ftCreate(index, IndexOptions.defaultOptions(), sc); + + // insert document(s) + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + fields.put("state", "NY"); + fields.put("body", "lorem ipsum"); + fields.put("price", "1337"); +// client.addDocument("doc1", fields); + addDocument("doc1", fields); + + // wrong aggregation query + AggregationBuilder builder = new AggregationBuilder("hello") + .apply("@price/1000", "k") + .groupBy("@state", Reducers.avg("@k").as("avgprice")) + .filter("@avgprice>=2") + .sortBy(10, SortedField.asc("@state")); + + try { + client.ftAggregate(index, builder); + fail(); + } catch (JedisDataException e) { + // should throw JedisDataException on wrong aggregation query + } + + } +} diff --git a/src/test/java/redis/clients/jedis/tests/modules/search/CreateTest.java b/src/test/java/redis/clients/jedis/tests/modules/search/CreateTest.java new file mode 100644 index 0000000000..410c21ba96 --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/modules/search/CreateTest.java @@ -0,0 +1,70 @@ +//package redis.clients.jedis.tests.modules.search; +// +//import static org.junit.Assert.*; +// +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.List; +//import org.junit.Test; +// +//import redis.clients.jedis.search.IndexDefinition; +//import redis.clients.jedis.search.IndexOptions; +// +//public class CreateTest { +// +// @Test +// public void defaultOptions() throws Exception { +// IndexOptions defaultOptions = IndexOptions.defaultOptions(); +// List arrayList = new ArrayList<>(); +// defaultOptions.serializeRedisArgs(arrayList); +// +// assertEquals(Arrays.asList(), arrayList); +// } +// +// @Test +// public void allOptions() throws Exception { +// IndexOptions defaultOptions = new Client.IndexOptions(0); +// +// defaultOptions.setStopwords("stop", "run"); +// defaultOptions.setTemporary(1234L); +// defaultOptions.setDefinition(new IndexDefinition()); +// +// List arrayList = new ArrayList<>(); +// defaultOptions.serializeRedisArgs(arrayList); +// +// assertEquals( +// Arrays.asList("NOOFFSETS", "NOFIELDS", "NOFREQS", "TEMPORARY", "1234", "STOPWORDS", "2", "stop", "run"), +// arrayList); +// } +// +// @Test +// public void allIndexDefinition() throws Exception { +// IndexDefinition indexRule = new IndexDefinition(IndexDefinition.Type.HASH); +// +// indexRule.setAsync(true); +// indexRule.setFilter("@sum<30"); +// indexRule.setLanguage("FR"); +// indexRule.setLanguageField("myLanguage"); +// indexRule.setPayloadField("myPayload"); +// indexRule.setPrefixes("person:"); +// indexRule.setScore(0.818656); +// indexRule.setScoreFiled("myScore"); +// +// List arrayList = new ArrayList<>(); +// indexRule.serializeRedisArgs(arrayList); +// +// assertEquals(Arrays.asList("ON", "HASH", "ASYNC", "PREFIX", "1", "person:", "FILTER", "@sum<30", "LANGUAGE_FIELD", +// "myLanguage", "LANGUAGE", "FR", "SCORE_FIELD", "myScore", "SCORE", "0.818656", "PAYLOAD_FIELD", "myPayload"), +// arrayList); +// +// assertEquals(true, indexRule.isAsync()); +// assertEquals("@sum<30", indexRule.getFilter()); +// assertEquals("FR", indexRule.getLanguage()); +// assertEquals("myLanguage", indexRule.getLanguageField()); +// assertEquals("myPayload", indexRule.getPayloadField()); +// assertArrayEquals(new String[]{"person:"}, indexRule.getPrefixes()); +// assertEquals(0.818656, indexRule.getScore(), 0.0); +// assertEquals("myScore", indexRule.getScoreFiled()); +// assertEquals(IndexDefinition.Type.HASH, indexRule.getType()); +// } +//} diff --git a/src/test/java/redis/clients/jedis/tests/modules/search/DocumentTest.java b/src/test/java/redis/clients/jedis/tests/modules/search/DocumentTest.java new file mode 100644 index 0000000000..5eefb0f244 --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/modules/search/DocumentTest.java @@ -0,0 +1,45 @@ +package redis.clients.jedis.tests.modules.search; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import redis.clients.jedis.search.Document; + +public class DocumentTest { + + @Test + public void serialize() throws IOException, ClassNotFoundException { + String id = "9f"; + double score = 10d; + Map map = new HashMap<>(); + map.put("string", "c"); + map.put("float", 12d); + byte[] payload = "1a".getBytes(); + Document document = new Document(id, map, score, payload); + + ByteArrayOutputStream aos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(aos); + oos.writeObject(document); + oos.flush(); + oos.close(); + + ByteArrayInputStream ais = new ByteArrayInputStream(aos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(ais); + Document read = (Document) ois.readObject(); + ois.close(); + + assertEquals(id, read.getId()); + assertEquals(score, read.getScore(), 0d); + assertArrayEquals(payload, read.getPayload()); + assertEquals("c", read.getString("string")); + assertEquals(Double.valueOf(12d), read.get("float")); + } +} diff --git a/src/test/java/redis/clients/jedis/tests/modules/search/JsonSearchTest.java b/src/test/java/redis/clients/jedis/tests/modules/search/JsonSearchTest.java new file mode 100644 index 0000000000..8e1acd08bd --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/modules/search/JsonSearchTest.java @@ -0,0 +1,225 @@ +package redis.clients.jedis.tests.modules.search; + +import static org.junit.Assert.*; + +import org.json.JSONObject; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import redis.clients.jedis.BuilderFactory; +import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.CommandObject; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.json.JsonProtocol; +import redis.clients.jedis.json.Path; +import redis.clients.jedis.search.*; +import redis.clients.jedis.search.Schema.*; +import redis.clients.jedis.search.SearchResult; +import redis.clients.jedis.tests.modules.RedisModuleCommandsTestBase; + +public class JsonSearchTest extends RedisModuleCommandsTestBase { + + public static final String JSON_ROOT = "$"; + + private static final String index = "json-index"; + + @BeforeClass + public static void prepare() { + RedisModuleCommandsTestBase.prepare(); + } + + @AfterClass + public static void tearDown() { +// RedisModuleCommandsTestBase.tearDown(); + } + + private void setJson(String key, JSONObject json) { + CommandObject command = new CommandObject<>(new CommandArguments(JsonProtocol.JsonCommand.SET) + .key(key).add(Path.ROOT_PATH).add(json), BuilderFactory.STRING); + client.executeCommand(command); + } + + private JSONObject toJson(Object... values) { + JSONObject json = new JSONObject(); + for (int i = 0; i < values.length; i += 2) { + json.put((String) values[i], values[i + 1]); + } + return json; + } + + @Test + public void create() { + Schema schema = new Schema().addTextField("$.first", 1.0).addTextField("$.last", 1.0) + .addNumericField("$.age"); + IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON) + .setPrefixes(new String[]{"student:", "pupil:"}); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema)); + +// try (Jedis jedis = client.connection()) { +// setJson(jedis, "profesor:5555", toJson("first", "Albert", "last", "Blue", "age", 55)); +// setJson(jedis, "student:1111", toJson("first", "Joe", "last", "Dod", "age", 18)); +// setJson(jedis, "pupil:2222", toJson("first", "Jen", "last", "Rod", "age", 14)); +// setJson(jedis, "student:3333", toJson("first", "El", "last", "Mark", "age", 17)); +// setJson(jedis, "pupil:4444", toJson("first", "Pat", "last", "Shu", "age", 21)); +// setJson(jedis, "student:5555", toJson("first", "Joen", "last", "Ko", "age", 20)); +// setJson(jedis, "teacher:6666", toJson("first", "Pat", "last", "Rod", "age", 20)); +// } + setJson("profesor:5555", toJson("first", "Albert", "last", "Blue", "age", 55)); + setJson("student:1111", toJson("first", "Joe", "last", "Dod", "age", 18)); + setJson("pupil:2222", toJson("first", "Jen", "last", "Rod", "age", 14)); + setJson("student:3333", toJson("first", "El", "last", "Mark", "age", 17)); + setJson("pupil:4444", toJson("first", "Pat", "last", "Shu", "age", 21)); + setJson("student:5555", toJson("first", "Joen", "last", "Ko", "age", 20)); + setJson("teacher:6666", toJson("first", "Pat", "last", "Rod", "age", 20)); + + SearchResult noFilters = client.ftSearch(index, new Query()); + assertEquals(5, noFilters.getTotalResults()); + + SearchResult res1 = client.ftSearch(index, new Query("@\\$\\.first:Jo*")); + assertEquals(2, res1.getTotalResults()); + + SearchResult res2 = client.ftSearch(index, new Query("@\\$\\.first:Pat")); + assertEquals(1, res2.getTotalResults()); + } + + @Test + public void createWithFieldNames() { + Schema schema = new Schema() + .addField(new TextField(FieldName.of("$.first").as("first"))) + .addField(new TextField(FieldName.of("$.last"))) + .addField(new Field(FieldName.of("$.age").as("age"), FieldType.NUMERIC)); + IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON) + .setPrefixes(new String[]{"student:", "pupil:"}); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema)); + +// try (Jedis jedis = client.connection()) { +// setJson(jedis, "profesor:5555", toJson("first", "Albert", "last", "Blue", "age", 55)); +// setJson(jedis, "student:1111", toJson("first", "Joe", "last", "Dod", "age", 18)); +// setJson(jedis, "pupil:2222", toJson("first", "Jen", "last", "Rod", "age", 14)); +// setJson(jedis, "student:3333", toJson("first", "El", "last", "Mark", "age", 17)); +// setJson(jedis, "pupil:4444", toJson("first", "Pat", "last", "Shu", "age", 21)); +// setJson(jedis, "student:5555", toJson("first", "Joen", "last", "Ko", "age", 20)); +// setJson(jedis, "teacher:6666", toJson("first", "Pat", "last", "Rod", "age", 20)); +// } + setJson("profesor:5555", toJson("first", "Albert", "last", "Blue", "age", 55)); + setJson("student:1111", toJson("first", "Joe", "last", "Dod", "age", 18)); + setJson("pupil:2222", toJson("first", "Jen", "last", "Rod", "age", 14)); + setJson("student:3333", toJson("first", "El", "last", "Mark", "age", 17)); + setJson("pupil:4444", toJson("first", "Pat", "last", "Shu", "age", 21)); + setJson("student:5555", toJson("first", "Joen", "last", "Ko", "age", 20)); + setJson("teacher:6666", toJson("first", "Pat", "last", "Rod", "age", 20)); + + SearchResult noFilters = client.ftSearch(index, new Query()); + assertEquals(5, noFilters.getTotalResults()); + + SearchResult asOriginal = client.ftSearch(index, new Query("@\\$\\.first:Jo*")); + assertEquals(0, asOriginal.getTotalResults()); + + SearchResult asAttribute = client.ftSearch(index, new Query("@first:Jo*")); + assertEquals(2, asAttribute.getTotalResults()); + + SearchResult nonAttribute = client.ftSearch(index, new Query("@\\$\\.last:Rod")); + assertEquals(1, nonAttribute.getTotalResults()); + } + + @Test + public void parseJson() { + Schema schema = new Schema(); + IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema)); + + String id = "student:1111"; + JSONObject json = toJson("first", "Joe", "last", "Dod", "age", 18); +// try (Jedis jedis = client.connection()) { +// setJson(jedis, id, json); +// } + setJson(id, json); + + // query + SearchResult sr = client.ftSearch(index, new Query().setWithScores().setWithPayload()); + assertEquals(1, sr.getTotalResults()); + + Document doc = sr.getDocuments().get(0); + assertEquals(Double.POSITIVE_INFINITY, doc.getScore(), 0); + assertNull(doc.getPayload()); + assertEquals(json.toString(), doc.get(JSON_ROOT)); + + // query repeat + sr = client.ftSearch(index, new Query().setWithScores().setWithPayload()); + + doc = sr.getDocuments().get(0); + JSONObject jsonRead = new JSONObject((String) doc.get(JSON_ROOT)); + assertEquals(json.toString(), jsonRead.toString()); + + // query repeat + sr = client.ftSearch(index, new Query().setWithScores().setWithPayload()); + + doc = sr.getDocuments().get(0); + jsonRead = new JSONObject(doc.getString(JSON_ROOT)); + assertEquals(json.toString(), jsonRead.toString()); + } + + @Test + public void parseJsonPartial() { + Schema schema = new Schema(); + IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema)); + + String id = "student:1111"; + JSONObject json = toJson("first", "Joe", "last", "Dod", "age", 18); +// try (Jedis jedis = client.connection()) { +// setJson(jedis, id, json); +// } + setJson(id, json); + + // query + SearchResult sr = client.ftSearch(index, new Query().returnFields("$.first", "$.last", "$.age")); + assertEquals(1, sr.getTotalResults()); + + Document doc = sr.getDocuments().get(0); + assertEquals("Joe", doc.get("$.first")); + assertEquals("Dod", doc.get("$.last")); + assertEquals(Integer.toString(18), doc.get("$.age")); + + // query repeat + sr = client.ftSearch(index, new Query().returnFields("$.first", "$.last", "$.age")); + + doc = sr.getDocuments().get(0); + assertEquals("Joe", doc.getString("$.first")); + assertEquals("Dod", doc.getString("$.last")); + assertEquals(18, Integer.parseInt((String) doc.get("$.age"))); + } + + @Test + public void parseJsonPartialWithFieldNames() { + Schema schema = new Schema(); + IndexDefinition rule = new IndexDefinition(IndexDefinition.Type.JSON); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), schema)); + + String id = "student:1111"; + JSONObject json = toJson("first", "Joe", "last", "Dod", "age", 18); +// try (Jedis jedis = client.connection()) { +// setJson(jedis, id, json); +// } + setJson(id, json); + + // query + SearchResult sr = client.ftSearch(index, new Query().returnFields(FieldName.of("$.first").as("first"), + FieldName.of("$.last").as("last"), FieldName.of("$.age"))); + assertEquals(1, sr.getTotalResults()); + + Document doc = sr.getDocuments().get(0); + assertNull(doc.get("$.first")); + assertNull(doc.get("$.last")); + assertEquals(Integer.toString(18), doc.get("$.age")); + assertEquals("Joe", doc.get("first")); + assertEquals("Dod", doc.get("last")); + assertNull(doc.get("age")); + } +} diff --git a/src/test/java/redis/clients/jedis/tests/modules/search/QueryTest.java b/src/test/java/redis/clients/jedis/tests/modules/search/QueryTest.java new file mode 100644 index 0000000000..916590e04d --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/modules/search/QueryTest.java @@ -0,0 +1,156 @@ +//package redis.clients.jedis.tests.modules.search; +// +//import static org.junit.Assert.*; +// +//import java.util.ArrayList; +//import org.junit.Before; +//import org.junit.Test; +//import redis.clients.jedis.search.Query; +// +///** +// * Created by dvirsky on 19/02/17. +// */ +//public class QueryTest { +// +// Query query; +// +// @Before +// public void setUp() throws Exception { +// query = new Query("hello world"); +// } +// +// @Test +// public void getNoContent() throws Exception { +// assertFalse(query.getNoContent()); +// assertEquals(query, query.setNoContent()); +// assertTrue(query.getNoContent()); +// } +// +// @Test +// public void getWithScores() throws Exception { +// assertFalse(query.getWithScores()); +// assertEquals(query, query.setWithScores()); +// assertTrue(query.getWithScores()); +// } +// +// @Test +// public void serializeRedisArgs() throws Exception { +// query.setNoContent().setLanguage("xx").setNoStopwords().setVerbatim().setWithPayload().setWithScores().setScorer("My.Scorer"); +// +// ArrayList args = new ArrayList<>(1); +// query.serializeRedisArgs(args); +// +// assertEquals(10, args.size()); +// assertEquals(query._queryString, new String(args.get(0))); +//// assertTrue(args.contains("xx".getBytes())); +//// assertTrue(args.contains("NOSTOPWORDS".getBytes())); +//// assertTrue(args.contains("VERBATIM".getBytes())); +//// assertTrue(args.contains("PAYLOADS".getBytes())); +//// assertTrue(args.contains("WITHSCORES".getBytes())); +// } +// +// @Test +// public void limit() throws Exception { +// assertEquals(0, query._paging.offset); +// assertEquals(10, query._paging.num); +// assertEquals(query, query.limit(1, 30)); +// assertEquals(1, query._paging.offset); +// assertEquals(30, query._paging.num); +// +// } +// +// @Test +// public void addFilter() throws Exception { +// assertEquals(0, query._filters.size()); +// Query.NumericFilter f = new Query.NumericFilter("foo", 0, 100); +// assertEquals(query, query.addFilter(f)); +// assertEquals(f, query._filters.get(0)); +// } +// +// @Test +// public void setVerbatim() throws Exception { +// assertFalse(query._verbatim); +// assertEquals(query, query.setVerbatim()); +// assertTrue(query._verbatim); +// } +// +// @Test +// public void setNoStopwords() throws Exception { +// assertFalse(query._noStopwords); +// assertEquals(query, query.setNoStopwords()); +// assertTrue(query._noStopwords); +// +// } +// +// @Test +// public void setLanguage() throws Exception { +// assertEquals(null, query._language); +// assertEquals(query, query.setLanguage("chinese")); +// assertEquals("chinese", query._language); +// } +// +// @Test +// public void setScorer() throws Exception { +// assertEquals(null, query._scorer); +// assertEquals(query, query.setScorer("the.scroer")); +// assertEquals("the.scroer", query._scorer); +// } +// +// @Test +// public void limitFields() throws Exception { +// assertNull(query._fields); +// assertEquals(query, query.limitFields("foo", "bar")); +// assertEquals(2, query._fields.length); +// } +// +// @Test +// public void returnFields() throws Exception { +// assertNull(query._returnFields); +// assertEquals(query, query.returnFields("foo", "bar")); +// assertEquals(2, query._returnFields.length); +// } +// +// @Test +// public void highlightFields() throws Exception { +// assertEquals(false, query.wantsHighlight); +// assertNull(query.highlightFields); +// +// query = new Query("Hello"); +// assertEquals(query, query.highlightFields("foo", "bar")); +// assertEquals(2, query.highlightFields.length); +// assertNull(query.highlightTags); +// assertEquals(true, query.wantsHighlight); +// +// query = new Query("Hello").highlightFields(); +// assertNull(query.highlightFields); +// assertNull(query.highlightTags); +// assertEquals(true, query.wantsHighlight); +// +// assertEquals(query, query.highlightFields(new Query.HighlightTags("", ""))); +// assertNull(query.highlightFields); +// assertEquals(2, query.highlightTags.length); +// assertEquals("", query.highlightTags[0]); +// assertEquals("", query.highlightTags[1]); +// } +// +// @Test +// public void summarizeFields() throws Exception { +// assertEquals(false, query.wantsSummarize); +// assertNull(query.summarizeFields); +// +// query = new Query("Hello"); +// assertEquals(query, query.summarizeFields()); +// assertEquals(true, query.wantsSummarize); +// assertNull(query.summarizeFields); +// assertEquals(-1, query.summarizeFragmentLen); +// assertEquals(-1, query.summarizeNumFragments); +// +// query = new Query("Hello"); +// assertEquals(query, query.summarizeFields("someField")); +// assertEquals(true, query.wantsSummarize); +// assertEquals(1, query.summarizeFields.length); +// assertEquals(-1, query.summarizeFragmentLen); +// assertEquals(-1, query.summarizeNumFragments); +// } +// +//} diff --git a/src/test/java/redis/clients/jedis/tests/modules/search/SchemaTest.java b/src/test/java/redis/clients/jedis/tests/modules/search/SchemaTest.java new file mode 100644 index 0000000000..263e6c4b1c --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/modules/search/SchemaTest.java @@ -0,0 +1,34 @@ +package redis.clients.jedis.tests.modules.search; + +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Test; +import redis.clients.jedis.search.Schema; + +/** + * Created by tgrall on 17/06/2020. + */ +public class SchemaTest { + + private final static String TITLE = "title"; + private final static String GENRE = "genre"; + private final static String VOTES = "votes"; + private final static String RATING = "rating"; + private final static String RELEASE_YEAR = "release_year"; + private final static String PLOT = "plot"; + + @Test + public void printSchemaTest() throws Exception { + Schema sc = new Schema() + .addTextField(TITLE, 5.0) + .addSortableTextField(PLOT, 1.0) + .addSortableTagField(GENRE, ",") + .addSortableNumericField(RELEASE_YEAR) + .addSortableNumericField(RATING) + .addSortableNumericField(VOTES); + + String schemaPrint = sc.toString(); + Assert.assertThat(schemaPrint, CoreMatchers.startsWith("Schema{fields=[TextField{name='title'")); + Assert.assertThat(schemaPrint, CoreMatchers.containsString("{name='release_year', type=NUMERIC, sortable=true, noindex=false}")); + } +} diff --git a/src/test/java/redis/clients/jedis/tests/modules/search/SearchTest.java b/src/test/java/redis/clients/jedis/tests/modules/search/SearchTest.java new file mode 100644 index 0000000000..adc51596df --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/modules/search/SearchTest.java @@ -0,0 +1,1381 @@ +package redis.clients.jedis.tests.modules.search; + +import static org.junit.Assert.*; +import static redis.clients.jedis.search.RediSearchUtil.toStringMap; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.search.*; +import redis.clients.jedis.search.Schema.*; +import redis.clients.jedis.tests.modules.RedisModuleCommandsTestBase; +import redis.clients.jedis.util.SafeEncoder; + +public class SearchTest extends RedisModuleCommandsTestBase { + + private static final String index = "testindex"; + + @BeforeClass + public static void prepare() { + RedisModuleCommandsTestBase.prepare(); + } + + @AfterClass + public static void tearDown() { +// RedisModuleCommandsTestBase.tearDown(); + } + + private void addDocument(String key, Map map) { + client.hset(key, toStringMap(map)); + } + + private static Map toMap(Object... values) { + Map map = new HashMap<>(); + for (int i = 0; i < values.length; i += 2) { + map.put((String) values[i], values[i + 1]); + } + return map; + } + + private static Map toMap(String... values) { + Map map = new HashMap<>(); + for (int i = 0; i < values.length; i += 2) { + map.put(values[i], values[i + 1]); + } + return map; + } + + @Test + public void creatDefinion() throws Exception { + Schema sc = new Schema().addTextField("first", 1.0).addTextField("last", 1.0).addNumericField("age"); + IndexDefinition rule = new IndexDefinition() + .setFilter("@age>16") + .setPrefixes(new String[]{"student:", "pupil:"}); + + try { + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc)); + } catch (JedisDataException e) { + // ON was only supported from RediSearch 2.0 + assertEquals("Unknown argument `ON`", e.getMessage()); + return; + } + +// try (Jedis jedis = client.connection()) { +// jedis.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55")); +// jedis.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18")); +// jedis.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14")); +// jedis.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17")); +// jedis.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21")); +// jedis.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20")); +// jedis.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20")); +// } + client.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55")); + client.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18")); + client.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14")); + client.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17")); + client.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21")); + client.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20")); + client.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20")); + + SearchResult noFilters = client.ftSearch(index, new Query()); + assertEquals(4, noFilters.getTotalResults()); + + SearchResult res1 = client.ftSearch(index, new Query("@first:Jo*")); + assertEquals(2, res1.getTotalResults()); + + SearchResult res2 = client.ftSearch(index, new Query("@first:Pat")); + assertEquals(1, res2.getTotalResults()); + + SearchResult res3 = client.ftSearch(index, new Query("@last:Rod")); + assertEquals(0, res3.getTotalResults()); + } + + @Test + public void withObjectMap() throws Exception { + Schema sc = new Schema().addTextField("first", 1.0).addTextField("last", 1.0).addNumericField("age"); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + +// try (Jedis jedis = client.connection()) { +// jedis.hset("student:1111", toStringMap(toMap("first", "Joe", "last", "Dod", "age", 18))); +// jedis.hset("student:3333", toStringMap(toMap("first", "El", "last", "Mark", "age", 17))); +// jedis.hset("pupil:4444", toStringMap(toMap("first", "Pat", "last", "Shu", "age", 21))); +// jedis.hset("student:5555", toStringMap(toMap("first", "Joen", "last", "Ko", "age", 20))); +// } + addDocument("student:1111", toMap("first", "Joe", "last", "Dod", "age", 18)); + addDocument("student:3333", toMap("first", "El", "last", "Mark", "age", 17)); + addDocument("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", 21)); + addDocument("student:5555", toMap("first", "Joen", "last", "Ko", "age", 20)); + + SearchResult noFilters = client.ftSearch(index, new Query()); + assertEquals(4, noFilters.getTotalResults()); + + SearchResult res1 = client.ftSearch(index, new Query("@first:Jo*")); + assertEquals(2, res1.getTotalResults()); + + SearchResult res2 = client.ftSearch(index, new Query("@first:Pat")); + assertEquals(1, res2.getTotalResults()); + + SearchResult res3 = client.ftSearch(index, new Query("@last:Rod")); + assertEquals(0, res3.getTotalResults()); + } + + @Test + public void createWithFieldNames() throws Exception { + Schema sc = new Schema().addField(new TextField(FieldName.of("first").as("given"))) + .addField(new TextField(FieldName.of("last"))); + IndexDefinition rule = new IndexDefinition() + //.setFilter("@age>16") + .setPrefixes(new String[]{"student:", "pupil:"}); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc)); + +// try (Jedis jedis = client.connection()) { +// jedis.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55")); +// jedis.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18")); +// jedis.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14")); +// jedis.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17")); +// jedis.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21")); +// jedis.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20")); +// jedis.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20")); +// } + client.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55")); + client.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18")); + client.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14")); + client.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17")); + client.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21")); + client.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20")); + client.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20")); + + SearchResult noFilters = client.ftSearch(index, new Query()); + assertEquals(5, noFilters.getTotalResults()); + + SearchResult asOriginal = client.ftSearch(index, new Query("@first:Jo*")); + assertEquals(0, asOriginal.getTotalResults()); + + SearchResult asAttribute = client.ftSearch(index, new Query("@given:Jo*")); + assertEquals(2, asAttribute.getTotalResults()); + + SearchResult nonAttribute = client.ftSearch(index, new Query("@last:Rod")); + assertEquals(1, nonAttribute.getTotalResults()); + } + + @Test + public void search() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0).addTextField("body", 1.0); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + fields.put("body", "lorem ipsum"); + for (int i = 0; i < 100; i++) { +// assertTrue(client.addDocument(String.format("doc%d", i), (double) i / 100.0, fields)); + addDocument(String.format("doc%d", i), fields); + } + + SearchResult res = client.ftSearch(index, new Query("hello world").limit(0, 5).setWithScores()); + assertEquals(100, res.getTotalResults()); + assertEquals(5, res.getDocuments().size()); + for (Document d : res.getDocuments()) { + assertTrue(d.getId().startsWith("doc")); + assertTrue(d.getScore() < 100); +// assertEquals( +// String.format( +// "{\"id\":\"%s\",\"score\":%s,\"properties\":{\"title\":\"hello world\",\"body\":\"lorem ipsum\"}}", +// d.getId(), Double.toString(d.getScore())), +// d.toString()); + } + +// assertTrue(client.deleteDocument("doc0", true)); +// assertFalse(client.deleteDocument("doc0")); + client.del("doc0"); + + res = client.ftSearch(index, new Query("hello world")); + assertEquals(99, res.getTotalResults()); + + assertEquals("OK", client.ftDropIndex(index)); + try { + client.ftSearch(index, new Query("hello world")); + fail(); + } catch (JedisDataException e) { + } + } +// +// @Test +// public void searchBatch() throws Exception { +// Schema sc = new Schema().addTextField("title", 1.0).addTextField("body", 1.0); +// +// assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); +// Map fields = new HashMap<>(); +// fields.put("title", "hello world"); +// fields.put("body", "lorem ipsum"); +// for (int i = 0; i < 50; i++) { +// fields.put("title", "hello world"); +//// assertTrue(client.addDocument(String.format("doc%d", i), (double) i / 100.0, fields)); +// addDocument(String.format("doc%d", i), fields); +// } +// +// for (int i = 50; i < 100; i++) { +// fields.put("title", "good night"); +//// assertTrue(client.addDocument(String.format("doc%d", i), (double) i / 100.0, fields)); +// addDocument(String.format("doc%d", i), fields); +// } +// +// SearchResult[] res = client.searchBatch( +// new Query("hello world").limit(0, 5).setWithScores(), +// new Query("good night").limit(0, 5).setWithScores() +// ); +// +// assertEquals(2, res.length); +// assertEquals(50, res[0].getTotalResults()); +// assertEquals(50, res[1].getTotalResults()); +// assertEquals(5, res[0].getDocuments().size()); +// for (Document d : res[0].getDocuments()) { +// assertTrue(d.getId().startsWith("doc")); +// assertTrue(d.getScore() < 100); +// assertEquals( +// String.format( +// "{\"id\":\"%s\",\"score\":%s,\"properties\":{\"title\":\"hello world\",\"body\":\"lorem ipsum\"}}", +// d.getId(), Double.toString(d.getScore())), +// d.toString()); +// } +// } + + @Test + public void testNumericFilter() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0).addNumericField("price"); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + + for (int i = 0; i < 100; i++) { + fields.put("price", i); +// assertTrue(client.addDocument(String.format("doc%d", i), fields)); + addDocument(String.format("doc%d", i), fields); + } + + SearchResult res = client.ftSearch(index, new Query("hello world"). + addFilter(new Query.NumericFilter("price", 0, 49))); + assertEquals(50, res.getTotalResults()); + assertEquals(10, res.getDocuments().size()); + for (Document d : res.getDocuments()) { + long price = Long.valueOf((String) d.get("price")); + assertTrue(price >= 0); + assertTrue(price <= 49); + } + + res = client.ftSearch(index, new Query("hello world"). + addFilter(new Query.NumericFilter("price", 0, true, 49, true))); + assertEquals(48, res.getTotalResults()); + assertEquals(10, res.getDocuments().size()); + for (Document d : res.getDocuments()) { + long price = Long.valueOf((String) d.get("price")); + assertTrue(price > 0); + assertTrue(price < 49); + } + res = client.ftSearch(index, new Query("hello world"). + addFilter(new Query.NumericFilter("price", 50, 100))); + assertEquals(50, res.getTotalResults()); + assertEquals(10, res.getDocuments().size()); + for (Document d : res.getDocuments()) { + long price = Long.valueOf((String) d.get("price")); + assertTrue(price >= 50); + assertTrue(price <= 100); + } + + res = client.ftSearch(index, new Query("hello world"). + addFilter(new Query.NumericFilter("price", 20, Double.POSITIVE_INFINITY))); + assertEquals(80, res.getTotalResults()); + assertEquals(10, res.getDocuments().size()); + + res = client.ftSearch(index, new Query("hello world"). + addFilter(new Query.NumericFilter("price", Double.NEGATIVE_INFINITY, 10))); + assertEquals(11, res.getTotalResults()); + assertEquals(10, res.getDocuments().size()); + + } + + @Test + public void testStopwords() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0); + + assertEquals("OK", client.ftCreate(index, + IndexOptions.defaultOptions().setStopwords("foo", "bar", "baz"), sc)); + + Map fields = new HashMap<>(); + fields.put("title", "hello world foo bar"); +// assertTrue(client.addDocument("doc1", fields)); + addDocument("doc1", fields); + SearchResult res = client.ftSearch(index, new Query("hello world")); + assertEquals(1, res.getTotalResults()); + res = client.ftSearch(index, new Query("foo bar")); + assertEquals(0, res.getTotalResults()); +// +// client.connection().flushDB(); +// +// assertEquals("OK", client.ftCreate(index, sc, +// IndexOptions.defaultOptions().setNoStopwords())); +// fields.put("title", "hello world foo bar to be or not to be"); +// assertTrue(client.addDocument("doc1", fields)); +// +// assertEquals(1, client.ftSearch(index, new Query("hello world")).getTotalResults()); +// assertEquals(1, client.ftSearch(index, new Query("foo bar")).getTotalResults()); +// assertEquals(1, client.ftSearch(index, new Query("to be or not to be")).getTotalResults()); + } + + @Test + public void testStopwordsMore() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0); + + assertEquals("OK", client.ftCreate(index, + IndexOptions.defaultOptions().setNoStopwords(), sc)); + Map fields = new HashMap<>(); + fields.put("title", "hello world foo bar"); + fields.put("title", "hello world foo bar to be or not to be"); + addDocument("doc1", fields); + + assertEquals(1, client.ftSearch(index, new Query("hello world")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("foo bar")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("to be or not to be")).getTotalResults()); + } + + @Test + public void testGeoFilter() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0).addGeoField("loc"); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + fields.put("loc", "-0.441,51.458"); +// assertTrue(client.addDocument("doc1", fields)); + addDocument("doc1", fields); + fields.put("loc", "-0.1,51.2"); +// assertTrue(client.addDocument("doc2", fields)); + addDocument("doc2", fields); + + SearchResult res = client.ftSearch(index, new Query("hello world"). + addFilter( + new Query.GeoFilter("loc", -0.44, 51.45, + 10, Query.GeoFilter.KILOMETERS) + )); + + assertEquals(1, res.getTotalResults()); + res = client.ftSearch(index, new Query("hello world"). + addFilter( + new Query.GeoFilter("loc", -0.44, 51.45, + 100, Query.GeoFilter.KILOMETERS) + )); + assertEquals(2, res.getTotalResults()); + } + + @Test + public void geoFilterAndGeoCoordinateObject() throws Exception { + Schema schema = new Schema().addTextField("title", 1.0).addGeoField("loc"); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), schema)); + + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + fields.put("loc", new redis.clients.jedis.GeoCoordinate(-0.441, 51.458)); +// assertTrue(client.addDocument("doc1", fields)); + addDocument("doc1", fields); + fields.put("loc", new redis.clients.jedis.GeoCoordinate(-0.1, 51.2)); +// assertTrue(client.addDocument("doc2", fields)); + addDocument("doc2", fields); + + SearchResult res = client.ftSearch(index, new Query("hello world").addFilter( + new Query.GeoFilter("loc", -0.44, 51.45, 10, Query.GeoFilter.KILOMETERS))); + assertEquals(1, res.getTotalResults()); + + res = client.ftSearch(index, new Query("hello world").addFilter( + new Query.GeoFilter("loc", -0.44, 51.45, 100, Query.GeoFilter.KILOMETERS))); + assertEquals(2, res.getTotalResults()); + } +// +// // TODO: This test was broken in master branch +// @Test +// public void testPayloads() throws Exception { +// Schema sc = new Schema().addTextField("title", 1.0); +// +// assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); +// +// Map fields = new HashMap<>(); +// fields.put("title", "hello world"); +// String payload = "foo bar"; +//// assertTrue(client.addDocument("doc1", 1.0, fields, false, false, payload.getBytes())); +// addDocument("doc1", fields); +// +// SearchResult res = client.ftSearch(index, new Query("hello world").setWithPayload()); +// assertEquals(1, res.getTotalResults()); +// assertEquals(1, res.getDocuments().size()); +// +// assertEquals(payload, new String(res.getDocuments().get(0).getPayload())); +// } + + @Test + public void testQueryFlags() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields = new HashMap<>(); + + for (int i = 0; i < 100; i++) { + fields.put("title", i % 2 != 0 ? "hello worlds" : "hello world"); +// assertTrue(client.addDocument(String.format("doc%d", i), (double) i / 100.0, fields)); + addDocument(String.format("doc%d", i), fields); + } + + Query q = new Query("hello").setWithScores(); + SearchResult res = client.ftSearch(index, q); + + assertEquals(100, res.getTotalResults()); + assertEquals(10, res.getDocuments().size()); + + for (Document d : res.getDocuments()) { + assertTrue(d.getId().startsWith("doc")); +// assertNotEquals(1.0, d.getScore()); + assertTrue(((String) d.get("title")).startsWith("hello world")); + } + + q = new Query("hello").setNoContent(); + res = client.ftSearch(index, q); + for (Document d : res.getDocuments()) { + assertTrue(d.getId().startsWith("doc")); + assertEquals(1.0, d.getScore(), 0); + assertEquals(null, d.get("title")); + } + + // test verbatim vs. stemming + res = client.ftSearch(index, new Query("hello worlds")); + assertEquals(100, res.getTotalResults()); + res = client.ftSearch(index, new Query("hello worlds").setVerbatim()); + assertEquals(50, res.getTotalResults()); + + res = client.ftSearch(index, new Query("hello a world").setVerbatim()); + assertEquals(50, res.getTotalResults()); + res = client.ftSearch(index, new Query("hello a worlds").setVerbatim()); + assertEquals(50, res.getTotalResults()); + res = client.ftSearch(index, new Query("hello a world").setVerbatim().setNoStopwords()); + assertEquals(0, res.getTotalResults()); + } + + @Test + public void testSortQueryFlags() throws Exception { + Schema sc = new Schema().addSortableTextField("title", 1.0); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields = new HashMap<>(); + + fields.put("title", "b title"); +// client.addDocument("doc1", 1.0, fields, false, true, null); + addDocument("doc1", fields); + + fields.put("title", "a title"); +// client.addDocument("doc2", 1.0, fields, false, true, null); + addDocument("doc2", fields); + + fields.put("title", "c title"); +// client.addDocument("doc3", 1.0, fields, false, true, null); + addDocument("doc3", fields); + + Query q = new Query("title").setSortBy("title", true); + SearchResult res = client.ftSearch(index, q); + + assertEquals(3, res.getTotalResults()); + Document doc1 = res.getDocuments().get(0); + assertEquals("a title", doc1.get("title")); + + doc1 = res.getDocuments().get(1); + assertEquals("b title", doc1.get("title")); + + doc1 = res.getDocuments().get(2); + assertEquals("c title", doc1.get("title")); + } + + @Test + public void testNullField() throws Exception { + Schema sc = new Schema() + .addTextField("title", 1.0) + .addTextField("genre", 1.0) + .addTextField("plot", 1.0) + .addSortableNumericField("release_year") + .addTagField("tag") + .addGeoField("loc"); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + // create a document with a field set to null + Map fields = new HashMap<>(); + fields.put("title", "another test with title "); + fields.put("genre", "Comedy"); + fields.put("plot", "this is the plot for the test"); + fields.put("tag", "fun"); + fields.put("release_year", 2019); + fields.put("loc", "-0.1,51.2"); + +// client.addDocument("doc1", fields); + addDocument("doc1", fields); + SearchResult res = client.ftSearch(index, new Query("title")); + assertEquals(1, res.getTotalResults()); + + fields = new HashMap<>(); + fields.put("title", "another title another test"); + fields.put("genre", "Action"); + fields.put("plot", null); + fields.put("tag", null); + + try { +// client.addDocument("doc2", fields); + addDocument("doc2", fields); + fail("Should throw NullPointerException."); + } catch (NullPointerException e) { +// assertEquals("Document attribute 'tag' is null. (Remove it, or set a value)", e.getMessage()); + } + + res = client.ftSearch(index, new Query("title")); + assertEquals(1, res.getTotalResults()); + + // Testing with numerical value + fields = new HashMap<>(); + fields.put("title", "another title another test"); + fields.put("genre", "Action"); + fields.put("release_year", null); + try { +// client.addDocument("doc2", fields); + addDocument("doc2", fields); + fail("Should throw NullPointerException."); + } catch (NullPointerException e) { +// assertEquals("Document attribute 'release_year' is null. (Remove it, or set a value)", e.getMessage()); + } + res = client.ftSearch(index, new Query("title")); + assertEquals(1, res.getTotalResults()); + } +// +// @Test +// public void testDrop() throws Exception { +// Schema sc = new Schema().addTextField("title", 1.0); +// +// assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); +// Map fields = new HashMap<>(); +// fields.put("title", "hello world"); +// for (int i = 0; i < 100; i++) { +//// assertTrue(client.addDocument(String.format("doc%d", i), fields)); +// addDocument(String.format("doc%d", i), fields); +// } +// +// SearchResult res = client.ftSearch(index, new Query("hello world")); +// assertEquals(100, res.getTotalResults()); +// +// assertEquals("OK", client.ftDropIndex(index)); +// +// Set keys = client.keys("*"); +// assertTrue(keys.isEmpty()); +// } + + @Test + public void dropIndex() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + for (int i = 0; i < 100; i++) { + addDocument(String.format("doc%d", i), fields); + } + + SearchResult res = client.ftSearch(index, new Query("hello world")); + assertEquals(100, res.getTotalResults()); + + assertEquals("OK", client.ftDropIndex(index)); + + try { + client.ftSearch(index, new Query("hello world")); + fail("Index should not exist."); + } catch (JedisDataException de) { + assertTrue(de.getMessage().contains("no such index")); + } + assertEquals(100, client.dbSize()); + } + + @Test + public void dropIndexDD() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + for (int i = 0; i < 100; i++) { + addDocument(String.format("doc%d", i), fields); + } + + SearchResult res = client.ftSearch(index, new Query("hello world")); + assertEquals(100, res.getTotalResults()); + + assertEquals("OK", client.ftDropIndexDD(index)); + + Set keys = client.keys("*"); + assertTrue(keys.isEmpty()); + assertEquals(0, client.dbSize()); + } + + @Test + public void testAlterAdd() throws Exception { + Schema sc = new Schema().addTextField("title", 1.0); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields = new HashMap<>(); + fields.put("title", "hello world"); + for (int i = 0; i < 100; i++) { + addDocument(String.format("doc%d", i), fields); + } + + SearchResult res = client.ftSearch(index, new Query("hello world")); + assertEquals(100, res.getTotalResults()); + + assertEquals("OK", client.ftAlter(index, new TagField("tags", ","), new TextField("name", 0.5))); + for (int i = 0; i < 100; i++) { + Map fields2 = new HashMap<>(); + fields2.put("name", "name" + i); + fields2.put("tags", String.format("tagA,tagB,tag%d", i)); +// assertTrue(client.updateDocument(String.format("doc%d", i), 1.0, fields2)); + addDocument(String.format("doc%d", i), fields2); + } + SearchResult res2 = client.ftSearch(index, new Query("@tags:{tagA}")); + assertEquals(100, res2.getTotalResults()); + + Map info = client.ftInfo(index); + assertEquals(index, info.get("index_name")); + assertEquals("identifier", ((List) ((List) info.get("attributes")).get(1)).get(0)); + assertEquals("attribute", ((List) ((List) info.get("attributes")).get(1)).get(2)); + } + + @Test + public void testNoStem() throws Exception { + Schema sc = new Schema().addTextField("stemmed", 1.0).addField(new Schema.TextField("notStemmed", 1.0, false, true)); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map doc = new HashMap<>(); + doc.put("stemmed", "located"); + doc.put("notStemmed", "located"); + // Store it +// assertTrue(client.addDocument("doc", doc)); + addDocument("doc", doc); + + // Query + SearchResult res = client.ftSearch(index, new Query("@stemmed:location")); + assertEquals(1, res.getTotalResults()); + + res = client.ftSearch(index, new Query("@notStemmed:location")); + assertEquals(0, res.getTotalResults()); + } + + @Test + public void testPhoneticMatch() throws Exception { + Schema sc = new Schema() + .addTextField("noPhonetic", 1.0) + .addField(new Schema.TextField("withPhonetic", 1.0, false, false, false, "dm:en")); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map doc = new HashMap<>(); + doc.put("noPhonetic", "morfix"); + doc.put("withPhonetic", "morfix"); + + // Store it +// assertTrue(client.addDocument("doc", doc)); + addDocument("doc", doc); + + // Query + SearchResult res = client.ftSearch(index, new Query("@withPhonetic:morphix=>{$phonetic:true}")); + assertEquals(1, res.getTotalResults()); + + try { + client.ftSearch(index, new Query("@noPhonetic:morphix=>{$phonetic:true}")); + fail(); + } catch (JedisDataException e) {/*field does not support phonetics*/ + } + + SearchResult res3 = client.ftSearch(index, new Query("@withPhonetic:morphix=>{$phonetic:false}")); + assertEquals(0, res3.getTotalResults()); + } + + @Test + public void testInfo() throws Exception { + String MOVIE_ID = "movie_id"; + String TITLE = "title"; + String GENRE = "genre"; + String VOTES = "votes"; + String RATING = "rating"; + String RELEASE_YEAR = "release_year"; + String PLOT = "plot"; + String POSTER = "poster"; + + Schema sc = new Schema() + .addTextField(TITLE, 5.0) + .addSortableTextField(PLOT, 1.0) + .addSortableTagField(GENRE, ",") + .addSortableNumericField(RELEASE_YEAR) + .addSortableNumericField(RATING) + .addSortableNumericField(VOTES); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map info = client.ftInfo(index); + assertEquals(index, info.get("index_name")); + + assertEquals(6, ((List) info.get("attributes")).size()); + assertEquals("global_idle", ((List) info.get("cursor_stats")).get(0)); + assertEquals(0L, ((List) info.get("cursor_stats")).get(1)); + } + + @Test + public void testNoIndex() throws Exception { + Schema sc = new Schema() + .addField(new Schema.TextField("f1", 1.0, true, false, true)) + .addField(new Schema.TextField("f2", 1.0)); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); + + Map mm = new HashMap<>(); + + mm.put("f1", "MarkZZ"); + mm.put("f2", "MarkZZ"); +// client.addDocument("doc1", mm); + addDocument("doc1", mm); + + mm.clear(); + mm.put("f1", "MarkAA"); + mm.put("f2", "MarkBB"); +// client.addDocument("doc2", mm); + addDocument("doc2", mm); + + SearchResult res = client.ftSearch(index, new Query("@f1:Mark*")); + assertEquals(0, res.getTotalResults()); + + res = client.ftSearch(index, new Query("@f2:Mark*")); + assertEquals(2, res.getTotalResults()); + + Document[] docs = new Document[2]; + + res = client.ftSearch(index, new Query("@f2:Mark*").setSortBy("f1", false)); + assertEquals(2, res.getTotalResults()); + + res.getDocuments().toArray(docs); + assertEquals("doc1", docs[0].getId()); + + res = client.ftSearch(index, new Query("@f2:Mark*").setSortBy("f1", true)); + res.getDocuments().toArray(docs); + assertEquals("doc2", docs[0].getId()); + } +// +// @Test +// public void testReplacePartial() throws Exception { +// Schema sc = new Schema() +// .addTextField("f1", 1.0) +// .addTextField("f2", 1.0) +// .addTextField("f3", 1.0); +// assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); +// +// Map mm = new HashMap<>(); +// mm.put("f1", "f1_val"); +// mm.put("f2", "f2_val"); +// +//// assertTrue(client.addDocument("doc1", mm)); +// addDocument("doc1", mm); +//// assertTrue(client.addDocument("doc2", mm)); +// addDocument("doc2", mm); +// +// mm.clear(); +// mm.put("f3", "f3_val"); +// +//// assertTrue(client.updateDocument("doc1", 1.0, mm)); +// addDocument("doc1", mm); +//// assertTrue(client.replaceDocument("doc2", 1.0, mm)); +// addDocument("doc2", mm); +// +// // Search for f3 value. All documents should have it. +// SearchResult res = client.ftSearch(index, new Query(("@f3:f3_Val"))); +// assertEquals(2, res.getTotalResults()); +// +// res = client.ftSearch(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); +// assertEquals(1, res.getTotalResults()); +// } +// +// @Test +// public void testReplaceIf() throws Exception { +// Schema sc = new Schema() +// .addTextField("f1", 1.0) +// .addTextField("f2", 1.0) +// .addTextField("f3", 1.0); +// assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); +// +// Map mm = new HashMap<>(); +// mm.put("f1", "v1_val"); +// mm.put("f2", "v2_val"); +// +// assertTrue(client.addDocument("doc1", mm)); +// assertTrue(client.addDocument("doc2", mm)); +// +// mm.clear(); +// mm.put("f3", "v3_val"); +// +// assertFalse(client.updateDocument("doc1", 1.0, mm, "@f1=='vv1_val'")); +// // Search for f3 value. No documents should not have it. +// SearchResult res1 = client.ftSearch(index, new Query(("@f3:f3_Val"))); +// assertEquals(0, res1.getTotalResults()); +// +// assertTrue(client.updateDocument("doc1", 1.0, mm, "@f2=='v2_val'")); +// // Search for f3 value. All documents should have it. +// SearchResult res2 = client.ftSearch(index, new Query(("@f3:v3_Val"))); +// assertEquals(1, res2.getTotalResults()); +// +// assertFalse(client.replaceDocument("doc2", 1.0, mm, "@f1=='vv3_Val'")); +// +// // Search for f3 value. Only one document should have it. +// SearchResult res3 = client.ftSearch(index, new Query(("@f3:v3_Val"))); +// assertEquals(1, res3.getTotalResults()); +// +// assertTrue(client.replaceDocument("doc2", 1.0, mm, "@f1=='v1_val'")); +// +// // Search for f3 value. All documents should have it. +// SearchResult res4 = client.ftSearch(index, new Query(("@f3:v3_Val"))); +// assertEquals(2, res4.getTotalResults()); +// } + + @Test + public void testExplain() throws Exception { + Schema sc = new Schema() + .addTextField("f1", 1.0) + .addTextField("f2", 1.0) + .addTextField("f3", 1.0); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); + + String res = client.ftExplain(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val")); + assertNotNull(res); + assertFalse(res.isEmpty()); + } + + @Test + public void testHighlightSummarize() throws Exception { + Schema sc = new Schema().addTextField("text", 1.0); + client.ftCreate(index, IndexOptions.defaultOptions(), sc); + + Map doc = new HashMap<>(); + doc.put("text", "Redis is often referred as a data structures server. What this means is that Redis provides access to mutable data structures via a set of commands, which are sent using a server-client model with TCP sockets and a simple protocol. So different processes can query and modify the same data structures in a shared way"); + // Add a document +// client.addDocument("foo", 1.0, doc); + addDocument("foo", doc); + Query q = new Query("data").highlightFields().summarizeFields(); + SearchResult res = client.ftSearch(index, q); + + assertEquals("is often referred as a data structures server. What this means is that Redis provides... What this means is that Redis provides access to mutable data structures via a set of commands, which are sent using a... So different processes can query and modify the same data structures in a shared... ", + res.getDocuments().get(0).get("text")); + + q = new Query("data").highlightFields(new Query.HighlightTags("", "")).summarizeFields(); + res = client.ftSearch(index, q); + + assertEquals("is often referred as a data structures server. What this means is that Redis provides... What this means is that Redis provides access to mutable data structures via a set of commands, which are sent using a... So different processes can query and modify the same data structures in a shared... ", + res.getDocuments().get(0).get("text")); + + } +// +// @Test +// public void testLanguage() throws Exception { +// Schema sc = new Schema().addTextField("text", 1.0); +// client.ftCreate(index, IndexOptions.defaultOptions(), sc); +// +// Document d = new Document("doc1").set("text", "hello"); +// AddOptions options = new AddOptions().setLanguage("spanish"); +// assertTrue(client.addDocument(d, options)); +// boolean caught = false; +// +// options.setLanguage("ybreski"); +// client.deleteDocument(d.getId()); +// +// try { +// client.addDocument(d, options); +// } catch (JedisDataException t) { +// caught = true; +// } +// assertTrue(caught); +// } + + @Test + public void testDropMissing() throws Exception { + try { + client.ftDropIndex(index); + fail(); + } catch (JedisDataException ex) { + } + } +// +// @Test +// public void testGet() throws Exception { +// client.ftCreate(index, IndexOptions.defaultOptions(), new Schema().addTextField("txt1", 1.0)); +// client.addDocument(new Document("doc1").set("txt1", "Hello World!"), new AddOptions()); +// Document d = client.getDocument("doc1"); +// assertNotNull(d); +// assertEquals("Hello World!", d.get("txt1")); +// +// // Get something that does not exist. Shouldn't explode +// assertNull(client.getDocument("nonexist")); +// +// // Test decode=false mode +// d = client.getDocument("doc1", false); +// assertNotNull(d); +// assertTrue(Arrays.equals(SafeEncoder.encode("Hello World!"), (byte[]) d.get("txt1"))); +// } +// +// @Test +// public void testMGet() throws Exception { +// client.ftCreate(index, IndexOptions.defaultOptions(), new Schema().addTextField("txt1", 1.0)); +// client.addDocument(new Document("doc1").set("txt1", "Hello World!1"), new AddOptions()); +// client.addDocument(new Document("doc2").set("txt1", "Hello World!2"), new AddOptions()); +// client.addDocument(new Document("doc3").set("txt1", "Hello World!3"), new AddOptions()); +// +// List docs = client.getDocuments(); +// assertEquals(0, docs.size()); +// +// docs = client.getDocuments("doc1", "doc3", "doc4"); +// assertEquals(3, docs.size()); +// assertEquals("Hello World!1", docs.get(0).get("txt1")); +// assertEquals("Hello World!3", docs.get(1).get("txt1")); +// assertNull(docs.get(2)); +// +// // Test decode=false mode +// docs = client.getDocuments(false, "doc2"); +// assertEquals(1, docs.size()); +// assertTrue(Arrays.equals(SafeEncoder.encode("Hello World!2"), (byte[]) docs.get(0).get("txt1"))); +// } +// +// @Test +// public void testDropDD() { +// client.ftCreate(index, IndexOptions.defaultOptions(), new Schema().addTextField("txt1", 1.0)); +// client.addDocument(new Document("doc1").set("txt1", "Hello World!1"), new AddOptions()); +// client.addDocument(new Document("doc2").set("txt1", "Hello World!2"), new AddOptions()); +// client.addDocument(new Document("doc3").set("txt1", "Hello World!3"), new AddOptions()); +// +// client.dropIndexDD(); +// try { +// client.getDocument("doc1"); +// } catch (JedisDataException jde) { +// assertUnknownIndex(jde); +// } +// try { +// client.getDocuments("doc2", "doc3"); +// } catch (JedisDataException jde) { +// assertUnknownIndex(jde); +// } +// try { +// client.dropIndexDD(); +// } catch (JedisDataException jde) { +// assertUnknownIndex(jde); +// } +// } +// +// private static void assertUnknownIndex(JedisDataException jde) { +// assertTrue(jde.getMessage().toLowerCase().contains("unknown index")); +// } +// +// @Test +// public void testAddSuggestionGetSuggestionFuzzy() throws Exception { +// Suggestion suggestion = Suggestion.builder().str("TOPIC OF WORDS").score(1).build(); +// // test can add a suggestion string +// assertTrue(suggestion.toString() + " insert should of returned at least 1", client.addSuggestion(suggestion, true) > 0); +// // test that the partial part of that string will be returned using fuzzy +// +// assertEquals(suggestion.toString() + " suppose to be returned", suggestion, client.getSuggestion(suggestion.getString().substring(0, 3), SuggestionOptions.builder().build()).get(0)); +// } +// +// @Test +// public void testAddSuggestionGetSuggestion() throws Exception { +// try { +// Suggestion.builder().str("ANOTHER_WORD").score(3).build(); +// fail("Illegal score"); +// } catch (IllegalStateException e) { +// } +// +// try { +// Suggestion.builder().score(1).build(); +// fail("Missing required string"); +// } catch (IllegalStateException e) { +// } +// +// Suggestion suggestion = Suggestion.builder().str("ANOTHER_WORD").score(1).build(); +// Suggestion noMatch = Suggestion.builder().str("_WORD MISSED").score(1).build(); +// +// assertTrue(suggestion.toString() + " should of inserted at least 1", client.addSuggestion(suggestion, false) > 0); +// assertTrue(noMatch.toString() + " should of inserted at least 1", client.addSuggestion(noMatch, false) > 0); +// +// // test that with a partial part of that string will have the entire word returned SuggestionOptions.builder().build() +// assertEquals(suggestion.getString() + " did not get a match with 3 characters", 1, client.getSuggestion(suggestion.getString().substring(0, 3), SuggestionOptions.builder().fuzzy().build()).size()); +// +// // turn off fuzzy start at second word no hit +// assertEquals(noMatch.getString() + " no fuzzy and starting at 1, should not match", 0, client.getSuggestion(noMatch.getString().substring(1, 6), SuggestionOptions.builder().build()).size()); +// // my attempt to trigger the fuzzy by 1 character +// assertEquals(noMatch.getString() + " fuzzy is on starting at 1 position should match", 1, client.getSuggestion(noMatch.getString().substring(1, 6), SuggestionOptions.builder().fuzzy().build()).size()); +// } +// +// @Test +// public void testAddSuggestionGetSuggestionPayloadScores() throws Exception { +// Suggestion suggestion = Suggestion.builder().str("COUNT_ME TOO").payload("PAYLOADS ROCK ").score(0.2).build(); +// assertTrue(suggestion.toString() + " insert should of at least returned 1", client.addSuggestion(suggestion, false) > 0); +// assertTrue("Count single added should return more than 1", client.addSuggestion(suggestion.toBuilder().str("COUNT").payload("My PAYLOAD is better").build(), false) > 1); +// assertTrue("Count single added should return more than 1", client.addSuggestion(suggestion.toBuilder().str("COUNT_ANOTHER").score(1).payload(null).build(), false) > 1); +// +// Suggestion noScoreOrPayload = Suggestion.builder().str("COUNT NO PAYLOAD OR COUNT").build(); +// assertTrue("Count single added should return more than 1", client.addSuggestion(noScoreOrPayload, true) > 1); +// +// List payloads = client.getSuggestion(suggestion.getString().substring(0, 3), SuggestionOptions.builder().with(SuggestionOptions.With.PAYLOAD_AND_SCORES).build()); +// assertEquals("4 suggestions with scores and payloads ", 4, payloads.size()); +// assertTrue("Assert that a suggestion has a payload ", payloads.get(2).getPayload().length() > 0); +// assertTrue("Assert that a suggestion has a score not default 1 ", payloads.get(1).getScore() < .299); +// +// } +// +// @Test +// public void testAddSuggestionGetSuggestionPayload() throws Exception { +// client.addSuggestion(Suggestion.builder().str("COUNT_ME TOO").payload("PAYLOADS ROCK ").build(), false); +// client.addSuggestion(Suggestion.builder().str("COUNT").payload("ANOTHER PAYLOAD ").build(), false); +// client.addSuggestion(Suggestion.builder().str("COUNTNO PAYLOAD OR COUNT").build(), false); +// +// // test that with a partial part of that string will have the entire word returned +// List payloads = client.getSuggestion("COU", SuggestionOptions.builder().max(3).fuzzy().with(SuggestionOptions.With.PAYLOAD).build()); +// assertEquals("3 suggestions payloads ", 3, payloads.size()); +// +// } +// +// @Test +// public void testGetSuggestionNoPayloadTwoOnly() throws Exception { +// client.addSuggestion(Suggestion.builder().str("DIFF_WORD").score(0.4).payload("PAYLOADS ROCK ").build(), false); +// client.addSuggestion(Suggestion.builder().str("DIFF wording").score(0.5).payload("ANOTHER PAYLOAD ").build(), false); +// client.addSuggestion(Suggestion.builder().str("DIFFERENT").score(0.7).payload("I am a payload").build(), false); +// +// List payloads = client.getSuggestion("DIF", SuggestionOptions.builder().max(2).build()); +// assertEquals("3 suggestions should match but only asking for 2 and payloads should have 2 items in array", 2, payloads.size()); +// +// List three = client.getSuggestion("DIF", SuggestionOptions.builder().max(3).build()); +// assertEquals("3 suggestions and payloads should have 3 items in array", 3, three.size()); +// +// } +// +// @Test +// public void testGetSuggestionWithScore() throws Exception { +// client.addSuggestion(Suggestion.builder().str("DIFF_WORD").score(0.4).payload("PAYLOADS ROCK ").build(), true); +// List list = client.getSuggestion("DIF", SuggestionOptions.builder().max(2).with(SuggestionOptions.With.SCORES).build()); +// assertTrue(list.get(0).getScore() <= .2); +// } +// +// @Test +// public void testGetSuggestionAllNoHit() throws Exception { +// client.addSuggestion(Suggestion.builder().str("NO WORD").score(0.4).build(), false); +// +// List none = client.getSuggestion("DIF", SuggestionOptions.builder().max(3).with(SuggestionOptions.With.SCORES).build()); +// assertEquals("Empty list not hit in index for partial word", 0, none.size()); +// } +// +// @Test +// public void testAddSuggestionDeleteSuggestionLength() throws Exception { +// client.addSuggestion(Suggestion.builder().str("TOPIC OF WORDS").score(1).build(), true); +// client.addSuggestion(Suggestion.builder().str("ANOTHER ENTRY").score(1).build(), true); +// +// long result = client.deleteSuggestion("ANOTHER ENTRY"); +// assertEquals("The delete of the suggestion should return 1", 1, result); +// assertEquals(1L, client.getSuggestionLength().longValue()); +// +// result = client.deleteSuggestion("ANOTHER ENTRY THAT IS NOT PRESENT"); +// assertEquals("The delete of the suggestion should return 0", 0, result); +// assertEquals(1L, client.getSuggestionLength().longValue()); +// } +// +// @Test +// public void testAddSuggestionGetSuggestionLength() throws Exception { +// client.addSuggestion(Suggestion.builder().str("TOPIC OF WORDS").score(1).build(), true); +// client.addSuggestion(Suggestion.builder().str("ANOTHER ENTRY").score(1).build(), true); +// assertEquals(2L, client.getSuggestionLength().longValue()); +// +// client.addSuggestion(Suggestion.builder().str("FINAL ENTRY").score(1).build(), true); +// assertEquals(3L, client.getSuggestionLength().longValue()); +// } + + @Test + public void testGetTagField() { + Schema sc = new Schema() + .addTextField("title", 1.0) + .addTagField("category"); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields1 = new HashMap<>(); + fields1.put("title", "hello world"); + fields1.put("category", "red"); +// assertTrue(client.addDocument("foo", fields1)); + addDocument("foo", fields1); + Map fields2 = new HashMap<>(); + fields2.put("title", "hello world"); + fields2.put("category", "blue"); +// assertTrue(client.addDocument("bar", fields2)); + addDocument("bar", fields2); + Map fields3 = new HashMap<>(); + fields3.put("title", "hello world"); + fields3.put("category", "green,yellow"); +// assertTrue(client.addDocument("baz", fields3)); + addDocument("baz", fields3); + Map fields4 = new HashMap<>(); + fields4.put("title", "hello world"); + fields4.put("category", "orange;purple"); +// assertTrue(client.addDocument("qux", fields4)); + addDocument("qux", fields4); + + assertEquals(1, client.ftSearch(index, new Query("@category:{red}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("@category:{blue}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("hello @category:{red}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("hello @category:{blue}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("@category:{yellow}")).getTotalResults()); + assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\;purple}")).getTotalResults()); + assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults()); + } + + @Test + public void testGetTagFieldWithNonDefaultSeparator() { + Schema sc = new Schema() + .addTextField("title", 1.0) + .addTagField("category", ";"); + + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map fields1 = new HashMap<>(); + fields1.put("title", "hello world"); + fields1.put("category", "red"); +// assertTrue(client.addDocument("foo", fields1)); + addDocument("foo", fields1); + Map fields2 = new HashMap<>(); + fields2.put("title", "hello world"); + fields2.put("category", "blue"); +// assertTrue(client.addDocument("bar", fields2)); + addDocument("bar", fields2); + Map fields3 = new HashMap<>(); + fields3.put("title", "hello world"); + fields3.put("category", "green;yellow"); + addDocument("baz", fields3); +// assertTrue(client.addDocument("baz", fields3)); + Map fields4 = new HashMap<>(); + fields4.put("title", "hello world"); + fields4.put("category", "orange,purple"); +// assertTrue(client.addDocument("qux", fields4)); + addDocument("qux", fields4); + + assertEquals(1, client.ftSearch(index, new Query("@category:{red}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("@category:{blue}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("hello @category:{red}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("hello @category:{blue}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("hello @category:{yellow}")).getTotalResults()); + assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults()); + assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\,purple}")).getTotalResults()); + assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults()); + } +// +// @Test +// public void testMultiDocuments() { +// Schema sc = new Schema().addTextField("title", 1.0).addTextField("body", 1.0); +// +// assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); +// +// Map fields = new HashMap<>(); +// fields.put("title", "hello world"); +// fields.put("body", "lorem ipsum"); +// +// boolean[] results = client.addDocuments(new Document("doc1", fields), new Document("doc2", fields), new Document("doc3", fields)); +// +// assertArrayEquals(new boolean[]{true, true, true}, results); +// +// assertEquals(3, client.ftSearch(index, new Query("hello world")).getTotalResults()); +// +// results = client.addDocuments(new Document("doc4", fields), new Document("doc2", fields), new Document("doc5", fields)); +// assertArrayEquals(new boolean[]{true, false, true}, results); +// +// results = client.deleteDocuments(true, "doc1", "doc2", "doc36"); +// assertArrayEquals(new boolean[]{true, true, false}, results); +// } + + @Test + public void testReturnFields() throws Exception { + Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map doc = new HashMap<>(); + doc.put("field1", "value1"); + doc.put("field2", "value2"); + // Store it +// assertTrue(client.addDocument("doc", doc)); + addDocument("doc", doc); + + // Query + SearchResult res = client.ftSearch(index, new Query("*").returnFields("field1")); + assertEquals(1, res.getTotalResults()); + assertEquals("value1", res.getDocuments().get(0).get("field1")); + assertNull(res.getDocuments().get(0).get("field2")); + } + + @Test + public void returnWithFieldNames() throws Exception { + Schema sc = new Schema().addTextField("a", 1).addTextField("b", 1).addTextField("c", 1); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map map = new HashMap<>(); + map.put("a", "value1"); + map.put("b", "value2"); + map.put("c", "value3"); +// assertTrue(client.addDocument("doc", map)); + addDocument("doc", map); + + // Query + SearchResult res = client.ftSearch(index, new Query() + .returnFields(FieldName.of("a"), FieldName.of("b").as("d"))); + assertEquals(1, res.getTotalResults()); + Document doc = res.getDocuments().get(0); + assertEquals("value1", doc.get("a")); + assertNull(doc.get("b")); + assertEquals("value2", doc.get("d")); + assertNull(doc.get("c")); + } + + @Test + public void testInKeys() throws Exception { + Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + Map doc = new HashMap<>(); + doc.put("field1", "value"); + doc.put("field2", "not"); + + // Store it +// assertTrue(client.addDocument("doc1", doc)); + addDocument("doc1", doc); +// assertTrue(client.addDocument("doc2", doc)); + addDocument("doc2", doc); + + // Query + SearchResult res = client.ftSearch(index, new Query("value").limitKeys("doc1")); + assertEquals(1, res.getTotalResults()); + assertEquals("doc1", res.getDocuments().get(0).getId()); + assertEquals("value", res.getDocuments().get(0).get("field1")); + assertEquals(null, res.getDocuments().get(0).get("value")); + } + + @Test + public void testBlobField() throws Exception { + Schema sc = new Schema().addTextField("field1", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + byte[] blob = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + Map doc = new HashMap<>(); + doc.put("field1", "value"); + doc.put("field2", blob); + + // Store it +// assertTrue(client.addDocument("doc1", doc)); + addDocument("doc1", doc); + + // Query +// SearchResult res = client.ftSearch(index, new Query("value"), false); + SearchResult res = client.ftSearch(SafeEncoder.encode(index), new Query("value")); + assertEquals(1, res.getTotalResults()); + assertEquals("doc1", res.getDocuments().get(0).getId()); + assertEquals("value", res.getDocuments().get(0).getString("field1")); + assertArrayEquals(blob, (byte[]) res.getDocuments().get(0).get("field2")); + } + + @Test +// public void testConfig() throws Exception { +// boolean result = client.setConfig(ConfigOption.TIMEOUT, "100"); +// assertTrue(result); +// Map configMap = client.getAllConfig(); +// assertEquals("100", configMap.get(ConfigOption.TIMEOUT.name())); +// assertEquals("100", client.getConfig(ConfigOption.TIMEOUT)); +// +// client.setConfig(ConfigOption.ON_TIMEOUT, "fail"); +// assertEquals("fail", client.getConfig(ConfigOption.ON_TIMEOUT)); +// +// try { +// assertFalse(client.setConfig(ConfigOption.ON_TIMEOUT, "null")); +// } catch (JedisDataException e) { +// // Should throw an exception after RediSearch 2.2 +// } +// } + public void testConfig() throws Exception { + assertEquals("OK", client.ftConfigSet("timeout", "100")); + Map configMap = client.ftConfigGet("*"); + assertEquals("100", configMap.get("TIMEOUT")); + } + + @Test + public void testAlias() throws Exception { + Schema sc = new Schema().addTextField("field1", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + Map doc = new HashMap<>(); + doc.put("field1", "value"); +// assertTrue(client.addDocument("doc1", doc)); + addDocument("doc1", doc); + + assertEquals("OK", client.ftAliasAdd("ALIAS1", index)); + SearchResult res1 = client.ftSearch("ALIAS1", new Query("*").returnFields("field1")); + assertEquals(1, res1.getTotalResults()); + assertEquals("value", res1.getDocuments().get(0).get("field1")); + + assertEquals("OK", client.ftAliasUpdate("ALIAS2", index)); + SearchResult res2 = client.ftSearch("ALIAS2", new Query("*").returnFields("field1")); + assertEquals(1, res2.getTotalResults()); + assertEquals("value", res2.getDocuments().get(0).get("field1")); + + try { + client.ftAliasDel("ALIAS3"); + fail("Should throw JedisDataException"); + } catch (JedisDataException e) { + // Alias does not exist + } + assertEquals("OK", client.ftAliasDel("ALIAS2")); + try { + client.ftAliasDel("ALIAS2"); + fail("Should throw JedisDataException"); + } catch (JedisDataException e) { + // Alias does not exist + } + } + + @Test + public void testSyn() throws Exception { + Schema sc = new Schema().addTextField("name", 1.0).addTextField("addr", 1.0); + assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc)); + + long group1, group2; + + group1 = 345L; + group2 = 789L; + assertEquals("OK", client.ftSynUpdate(index, group1, "girl", "baby")); + assertEquals("OK", client.ftSynUpdate(index, group1, "child")); + assertEquals("OK", client.ftSynUpdate(index, group2, "child")); + +// Map> dump = client.dumpSynonym(); +// +// Map> expected = new HashMap<>(); +// expected.put("girl", Arrays.asList(String.valueOf(group1))); +// expected.put("baby", Arrays.asList(String.valueOf(group1))); +// expected.put("child", Arrays.asList(String.valueOf(group1), String.valueOf(group2))); +// assertEquals(expected, dump); +// + Map> dump = client.ftSynDump(index); + assertEquals(3, dump.size()); +// assertEquals(dump.get("girl"), Arrays.asList(group1)); +// assertEquals(dump.get("baby"), Arrays.asList(group1)); +// assertEquals(dump.get("child"), Arrays.asList(group1, group2)); + assertEquals(1, dump.get("girl").size()); + assertEquals(1, dump.get("baby").size()); + assertEquals(2, dump.get("child").size()); + } +}