diff --git a/src/main/java/redis/clients/jedis/BuilderFactory.java b/src/main/java/redis/clients/jedis/BuilderFactory.java index 65dd4ae0e3..791504014c 100644 --- a/src/main/java/redis/clients/jedis/BuilderFactory.java +++ b/src/main/java/redis/clients/jedis/BuilderFactory.java @@ -6,6 +6,7 @@ import redis.clients.jedis.resps.LCSMatchResult.MatchedPosition; import redis.clients.jedis.resps.LCSMatchResult.Position; import redis.clients.jedis.resps.*; +import redis.clients.jedis.search.aggr.AggregationResult; import redis.clients.jedis.stream.*; import redis.clients.jedis.util.JedisByteHashMap; import redis.clients.jedis.util.SafeEncoder; @@ -1221,7 +1222,7 @@ public LCSMatchResult build(Object data) { } }; - public static final Builder> REDIS_JSON_TYPE = new Builder>() { + public static final Builder> JSON_TYPE = new Builder>() { @Override public Class build(Object data) { String str = STRING.build(data); @@ -1251,6 +1252,33 @@ public String toString() { } }; + public static final Builder SEARCH_AGGREGATION_RESULT = new Builder() { + @Override + public AggregationResult build(Object data) { + return new AggregationResult(data); + } + }; + + public static final Builder SEARCH_AGGREGATION_RESULT_WITH_CURSOR = new Builder() { + @Override + public AggregationResult build(Object data) { + List list = (List) data; + return new AggregationResult(list.get(0), (long) list.get(1)); + } + }; + + public static final Builder>> SEARCH_SYNONYM_GROUPS = new Builder>>() { + @Override + public Map> build(Object data) { + List list = (List) data; + Map> dump = new HashMap<>(list.size() / 2); + for (int i = 0; i < list.size(); i += 2) { + dump.put(STRING.build(list.get(i)), ENCODED_OBJECT_LIST.build(list.get(i + 1))); + } + return dump; + } + }; + /** * A decorator to implement Set from List. Assume that given List do not contains duplicated * values. The resulting set displays the same ordering, concurrency, and performance diff --git a/src/main/java/redis/clients/jedis/RedisCommandObjects.java b/src/main/java/redis/clients/jedis/RedisCommandObjects.java index 7cc2a9bcc9..84f36c4f98 100644 --- a/src/main/java/redis/clients/jedis/RedisCommandObjects.java +++ b/src/main/java/redis/clients/jedis/RedisCommandObjects.java @@ -23,6 +23,8 @@ import redis.clients.jedis.search.SearchProtocol.SearchCommand; import redis.clients.jedis.search.SearchProtocol.SearchKeyword; import redis.clients.jedis.search.SearchResult.SearchResultBuilder; +import redis.clients.jedis.search.aggr.AggregationBuilder; +import redis.clients.jedis.search.aggr.AggregationResult; import redis.clients.jedis.stream.*; public class RedisCommandObjects { @@ -2443,6 +2445,13 @@ public CommandObject ftCreate(String indexName, IndexOptions indexOption return new CommandObject<>(args, BuilderFactory.STRING); } + public final 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)); + return new CommandObject<>(args, BuilderFactory.STRING); + } + public CommandObject ftSearch(String indexName, Query query) { return new CommandObject<>(commandArguments(SearchCommand.SEARCH).add(indexName).addParams(query), new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), query.getWithPayloads(), true)); @@ -2452,6 +2461,29 @@ public CommandObject ftSearch(byte[] indexName, Query query) { return new CommandObject<>(commandArguments(SearchCommand.SEARCH).add(indexName).addParams(query), new SearchResultBuilder(!query.getNoContent(), query.getWithScores(), query.getWithPayloads(), false)); } + + public final 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) { + return new CommandObject<>(commandArguments(SearchCommand.DROPINDEX).add(indexName), BuilderFactory.STRING); + } + + public final 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) { + return new CommandObject<>(commandArguments(SearchCommand.SYNUPDATE).add(indexName) + .add(synonymGroupId).addObjects((Object[]) terms), BuilderFactory.STRING); + } + + public final CommandObject>> ftSynDump(String indexName) { + return new CommandObject<>(commandArguments(SearchCommand.SYNDUMP).add(indexName), BuilderFactory.SEARCH_SYNONYM_GROUPS); + } + // RediSearch commands // RedisJSON commands @@ -2508,11 +2540,11 @@ public final CommandObject jsonToggle(String key, Path path) { } public final CommandObject> jsonType(String key) { - return new CommandObject<>(commandArguments(JsonCommand.TYPE).key(key), BuilderFactory.REDIS_JSON_TYPE); + return new CommandObject<>(commandArguments(JsonCommand.TYPE).key(key), BuilderFactory.JSON_TYPE); } public final CommandObject> jsonType(String key, Path path) { - return new CommandObject<>(commandArguments(JsonCommand.TYPE).key(key).add(path), BuilderFactory.REDIS_JSON_TYPE); + return new CommandObject<>(commandArguments(JsonCommand.TYPE).key(key).add(path), BuilderFactory.JSON_TYPE); } public final CommandObject jsonStrAppend(String key, Path path, Object... objects) { diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index c4863d8b19..d694d50d3d 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -24,6 +24,8 @@ import redis.clients.jedis.search.SearchResult; import redis.clients.jedis.commands.JedisCommands; import redis.clients.jedis.commands.JedisBinaryCommands; +import redis.clients.jedis.search.aggr.AggregationBuilder; +import redis.clients.jedis.search.aggr.AggregationResult; public class UnifiedJedis implements JedisCommands, JedisBinaryCommands, SampleKeyedCommands, SampleBinaryKeyedCommands, RedisModuleCommands, @@ -2784,6 +2786,11 @@ public String ftCreate(String indexName, IndexOptions indexOptions, Schema schem return executeCommand(commandObjects.ftCreate(indexName, indexOptions, schema)); } + @Override + public String ftAlter(String indexName, Schema schema) { + return executeCommand(commandObjects.ftAlter(indexName, schema)); + } + @Override public SearchResult ftSearch(String indexName, Query query) { return executeCommand(commandObjects.ftSearch(indexName, query)); @@ -2793,6 +2800,72 @@ public SearchResult ftSearch(String indexName, Query query) { public SearchResult ftSearch(byte[] indexName, Query query) { return executeCommand(commandObjects.ftSearch(indexName, query)); } + + @Override + public String ftExplain(String indexName, Query query) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List ftExplainCLI(String indexName, Query query) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public AggregationResult ftAggregate(String indexName, AggregationBuilder aggr) { + return executeCommand(commandObjects.ftAggregate(indexName, aggr)); + } + + @Override + public String ftDropIndex(String indexName) { + return executeCommand(commandObjects.ftDropIndex(indexName)); + } + + @Override + public String ftDropIndexDD(String indexName) { + return executeCommand(commandObjects.ftDropIndexDD(indexName)); + } + + @Override + public String ftSynUpdate(String indexName, String synonymGroupId, String... terms) { + return executeCommand(commandObjects.ftSynUpdate(indexName, synonymGroupId, terms)); + } + + @Override + public Map> ftSynDump(String indexName) { + return executeCommand(commandObjects.ftSynDump(indexName)); + } + + @Override + public Map ftInfo(String indexName) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String ftAliasAdd(String aliasName, String indexName) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String ftAliasUpdate(String aliasName, String indexName) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String ftAliasDel(String aliasName) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public Map ftConfigGet(String option) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String ftConfigSet(String option, String value) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + // RediSearch commands // RedisJSON commands diff --git a/src/main/java/redis/clients/jedis/search/FieldName.java b/src/main/java/redis/clients/jedis/search/FieldName.java index 50ba6b4bd7..c66ea25801 100644 --- a/src/main/java/redis/clients/jedis/search/FieldName.java +++ b/src/main/java/redis/clients/jedis/search/FieldName.java @@ -1,6 +1,6 @@ package redis.clients.jedis.search; -import java.util.Collection; +import java.util.List; import redis.clients.jedis.CommandArguments; import redis.clients.jedis.params.IParams; @@ -8,8 +8,8 @@ public class FieldName implements IParams { -// private static final String AS_ENCODED = "AS"; -// private static final byte[] AS_BINARY = SafeEncoder.encode(AS_ENCODED); + private static final String AS_ENCODED = "AS"; + private static final byte[] AS_BINARY = SafeEncoder.encode(AS_ENCODED); private static final byte[] AS = SafeEncoder.encode("AS"); private final String name; @@ -23,28 +23,28 @@ public FieldName(String name, String attribute) { this.name = name; this.attribute = attribute; } -// -// public int addCommandEncodedArguments(Collection args) { -// args.add(name); -// if (attribute == null) { -// return 1; -// } -// -// args.add(AS_ENCODED); -// args.add(attribute); -// return 3; -// } -// -// public int addCommandBinaryArguments(Collection args) { -// args.add(SafeEncoder.encode(name)); -// if (attribute == null) { -// return 1; -// } -// -// args.add(AS_BINARY); -// args.add(SafeEncoder.encode(attribute)); -// return 3; -// } + + public int addCommandEncodedArguments(List args) { + args.add(name); + if (attribute == null) { + return 1; + } + + args.add(AS_ENCODED); + args.add(attribute); + return 3; + } + + public int addCommandBinaryArguments(List args) { + args.add(SafeEncoder.encode(name)); + if (attribute == null) { + return 1; + } + + args.add(AS_BINARY); + args.add(SafeEncoder.encode(attribute)); + return 3; + } public int addCommandArguments(CommandArguments args) { args.add(SafeEncoder.encode(name)); diff --git a/src/main/java/redis/clients/jedis/search/Query.java b/src/main/java/redis/clients/jedis/search/Query.java index 0188f4227b..21aaf568bb 100644 --- a/src/main/java/redis/clients/jedis/search/Query.java +++ b/src/main/java/redis/clients/jedis/search/Query.java @@ -1,10 +1,9 @@ package redis.clients.jedis.search; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import redis.clients.jedis.CommandArguments; +import redis.clients.jedis.CommandArguments; import redis.clients.jedis.Protocol; import redis.clients.jedis.args.Rawable; import redis.clients.jedis.params.IParams; @@ -51,13 +50,6 @@ public NumericFilter(String property, double min, double max) { } private byte[] formatNum(double num, boolean exclude) { - if (num == Double.POSITIVE_INFINITY) { - return SearchKeyword.POSITIVE_INFINITY.getRaw(); - } - if (num == Double.NEGATIVE_INFINITY) { - return SearchKeyword.NEGATIVE_INFINITY.getRaw(); - } - return exclude ? SafeEncoder.encode("(" + num) : Protocol.toByteArray(num); } diff --git a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java index d641fac058..45a3c77c12 100644 --- a/src/main/java/redis/clients/jedis/search/RediSearchCommands.java +++ b/src/main/java/redis/clients/jedis/search/RediSearchCommands.java @@ -1,10 +1,47 @@ package redis.clients.jedis.search; +import java.util.List; +import java.util.Map; +import redis.clients.jedis.search.aggr.AggregationBuilder; +import redis.clients.jedis.search.aggr.AggregationResult; + public interface RediSearchCommands { String ftCreate(String indexName, IndexOptions indexOptions, Schema schema); + default String ftAlter(String indexName, Schema.Field... fields) { + return ftAlter(indexName, Schema.from(fields)); + } + + String ftAlter(String indexName, Schema schema); + SearchResult ftSearch(String indexName, Query query); SearchResult ftSearch(byte[] indexName, Query query); + + String ftExplain(String indexName, Query query); + + List ftExplainCLI(String indexName, Query query); + + AggregationResult ftAggregate(String indexName, AggregationBuilder aggr); + + String ftDropIndex(String indexName); + + String ftDropIndexDD(String indexName); + + String ftSynUpdate(String indexName, String synonymGroupId, String... terms); + + Map> ftSynDump(String indexName); + + Map ftInfo(String indexName); + + String ftAliasAdd(String aliasName, String indexName); + + String ftAliasUpdate(String aliasName, String indexName); + + String ftAliasDel(String aliasName); + + Map ftConfigGet(String option); + + String ftConfigSet(String option, String value); } diff --git a/src/main/java/redis/clients/jedis/search/Schema.java b/src/main/java/redis/clients/jedis/search/Schema.java index 4389218ed1..8b53bea10c 100644 --- a/src/main/java/redis/clients/jedis/search/Schema.java +++ b/src/main/java/redis/clients/jedis/search/Schema.java @@ -24,6 +24,14 @@ public Schema() { this.fields = new ArrayList<>(); } + public static Schema from(Field... fields) { + Schema schema = new Schema(); + for (Field field : fields) { + schema.addField(field); + } + return schema; + } + /** * Add a text field to the schema with a given weight * @@ -130,16 +138,7 @@ public Field(FieldName name, FieldType type, boolean sortable, boolean noIndex) } @Override - public void addParams(CommandArguments args) { - - } - - /** - * Sub-classes should not override this method. - * - * @param args - */ - public void serializeRedisArgs(CommandArguments args) { + public final void addParams(CommandArguments args) { this.fieldName.addParams(args); args.add(type.name()); addTypeArgs(args); diff --git a/src/main/java/redis/clients/jedis/search/SearchProtocol.java b/src/main/java/redis/clients/jedis/search/SearchProtocol.java index c55ef221d5..0b03c78856 100644 --- a/src/main/java/redis/clients/jedis/search/SearchProtocol.java +++ b/src/main/java/redis/clients/jedis/search/SearchProtocol.java @@ -10,14 +10,9 @@ public enum SearchCommand implements ProtocolCommand { CREATE("FT.CREATE"), ALTER("FT.ALTER"), - ADD("FT.ADD"), INFO("FT.INFO"), SEARCH("FT.SEARCH"), EXPLAIN("FT.EXPLAIN"), - DEL("FT.DEL"), - DROP("FT.DROP"), - GET("FT.GET"), - MGET("FT.MGET"), AGGREGATE("FT.AGGREGATE"), CURSOR("FT.CURSOR"), CONFIG("FT.CONFIG"), @@ -25,7 +20,12 @@ public enum SearchCommand implements ProtocolCommand { ALIASUPDATE("FT.ALIASUPDATE"), ALIASDEL("FT.ALIASDEL"), SYNUPDATE("FT.SYNUPDATE"), - SYNDUMP("FT.SYNDUMP"); + SYNDUMP("FT.SYNDUMP"), +// SUGADD("FT.SUGADD"), +// SUGGET("FT.SUGGET"), +// SUGDEL("FT.SUGDEL"), +// SUGLEN("FT.SUGLEN"), + DROPINDEX("FT.DROPINDEX"); private final byte[] raw; @@ -41,20 +41,16 @@ public byte[] getRaw() { 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, POSITIVE_INFINITY("+inf"), NEGATIVE_INFINITY("-inf"), - 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; + 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; private final byte[] raw; - private SearchKeyword(String keyword) { - raw = SafeEncoder.encode(keyword); - } - private SearchKeyword() { - raw = SafeEncoder.encode(this.name()); + raw = SafeEncoder.encode(name()); } @Override diff --git a/src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java b/src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java new file mode 100644 index 0000000000..a7d0c6c076 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/AggregationBuilder.java @@ -0,0 +1,146 @@ +package redis.clients.jedis.search.aggr; + +import java.util.*; +import redis.clients.jedis.search.FieldName; +import redis.clients.jedis.util.SafeEncoder; + +/** + * @author Guy Korland + */ +public class AggregationBuilder { + + private final List args = new ArrayList<>(); + private boolean isWithCursor = false; + + public AggregationBuilder(String query) { + args.add(query); + } + + public AggregationBuilder() { + this("*"); + } + + public AggregationBuilder load(String... fields) { + return load(FieldName.convert(fields)); + } + + public AggregationBuilder load(FieldName... fields) { + args.add("LOAD"); + final int loadCountIndex = args.size(); + args.add(null); + int loadCount = 0; + for (FieldName fn : fields) { + loadCount += fn.addCommandEncodedArguments(args); + } + args.set(loadCountIndex, Integer.toString(loadCount)); + return this; + } + + public AggregationBuilder limit(int offset, int count) { + Limit limit = new Limit(offset, count); + limit.addArgs(args); + return this; + } + + public AggregationBuilder limit(int count) { + return limit(0, count); + } + + public AggregationBuilder sortBy(SortedField... fields) { + args.add("SORTBY"); + args.add(Integer.toString(fields.length * 2)); + for (SortedField field : fields) { + args.add(field.getField()); + args.add(field.getOrder()); + } + + return this; + } + + public AggregationBuilder sortBy(int max, SortedField... fields) { + sortBy(fields); + if (max > 0) { + args.add("MAX"); + args.add(Integer.toString(max)); + } + return this; + } + + public AggregationBuilder sortByAsc(String field) { + return sortBy(SortedField.asc(field)); + } + + public AggregationBuilder sortByDesc(String field) { + return sortBy(SortedField.desc(field)); + } + + public AggregationBuilder apply(String projection, String alias) { + args.add("APPLY"); + args.add(projection); + args.add("AS"); + args.add(alias); + return this; + } + + public AggregationBuilder groupBy(Collection fields, Collection reducers) { + String[] fieldsArr = new String[fields.size()]; + Group g = new Group(fields.toArray(fieldsArr)); + for (Reducer r : reducers) { + g.reduce(r); + } + groupBy(g); + return this; + } + + public AggregationBuilder groupBy(String field, Reducer... reducers) { + return groupBy(Collections.singletonList(field), Arrays.asList(reducers)); + } + + public AggregationBuilder groupBy(Group group) { + args.add("GROUPBY"); + group.addArgs(args); + return this; + } + + public AggregationBuilder filter(String expression) { + args.add("FILTER"); + args.add(expression); + return this; + } + + public AggregationBuilder cursor(int count, long maxIdle) { + isWithCursor = true; + if (count > 0) { + args.add("WITHCURSOR"); + args.add("COUNT"); + args.add(Integer.toString(count)); + if (maxIdle < Long.MAX_VALUE && maxIdle >= 0) { + args.add("MAXIDLE"); + args.add(Long.toString(maxIdle)); + } + } + return this; + } + + public List getArgs() { + return Collections.unmodifiableList(args); + } + + public void serializeRedisArgs(List redisArgs) { + for (String s : getArgs()) { + redisArgs.add(SafeEncoder.encode(s)); + } + } + + public String getArgsString() { + StringJoiner sj = new StringJoiner(" "); + for (String s : getArgs()) { + sj.add(s); + } + return sj.toString(); + } + + public boolean isWithCursor() { + return isWithCursor; + } +} diff --git a/src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java b/src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java new file mode 100644 index 0000000000..cda4d6a4a1 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/AggregationResult.java @@ -0,0 +1,58 @@ +package redis.clients.jedis.search.aggr; + +import redis.clients.jedis.exceptions.JedisDataException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by mnunberg on 2/22/18. + */ +public class AggregationResult { + + public final long totalResults; + + private long cursorId = -1; + private final List> results = new ArrayList<>(); + + public AggregationResult(Object resp, long cursorId) { + this(resp); + this.cursorId = cursorId; + } + + public AggregationResult(Object resp) { + List list = (List) resp; + // the first element is always the number of results + totalResults = (Long) list.get(0); + + for (int i = 1; i < list.size(); i++) { + List raw = (List) list.get(i); + Map cur = new HashMap<>(); + for (int j = 0; j < raw.size(); j += 2) { + Object r = raw.get(j); + if (r instanceof JedisDataException) { + throw (JedisDataException) r; + } + cur.put(new String((byte[]) r), raw.get(j + 1)); + } + results.add(cur); + } + } + + public List> getResults() { + return results; + } + + public Row getRow(int index) { + if (index >= results.size()) { + return null; + } + return new Row(results.get(index)); + } + + public long getCursorId() { + return cursorId; + } +} diff --git a/src/main/java/redis/clients/jedis/search/aggr/Group.java b/src/main/java/redis/clients/jedis/search/aggr/Group.java new file mode 100644 index 0000000000..a60cb5ac0c --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/Group.java @@ -0,0 +1,51 @@ +package redis.clients.jedis.search.aggr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by mnunberg on 2/22/18. + */ +public class Group { + + private final List reducers = new ArrayList<>(); + private final List fields = new ArrayList<>(); + private Limit limit = new Limit(0, 0); + + public Group(String... fields) { + this.fields.addAll(Arrays.asList(fields)); + } + + public Group reduce(Reducer r) { + reducers.add(r); + return this; + } + + public Group limit(Limit limit) { + this.limit = limit; + return this; + } + + public void addArgs(List args) { + args.add(Integer.toString(fields.size())); + args.addAll(fields); + for (Reducer r : reducers) { + args.add("REDUCE"); + args.add(r.getName()); + r.addArgs(args); + String alias = r.getAlias(); + if (alias != null && !alias.isEmpty()) { + args.add("AS"); + args.add(alias); + } + } + args.addAll(limit.getArgs()); + } + + public List getArgs() { + List args = new ArrayList<>(); + addArgs(args); + return args; + } +} diff --git a/src/main/java/redis/clients/jedis/search/aggr/Limit.java b/src/main/java/redis/clients/jedis/search/aggr/Limit.java new file mode 100644 index 0000000000..9f512c9713 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/Limit.java @@ -0,0 +1,39 @@ +package redis.clients.jedis.search.aggr; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by mnunberg on 2/22/18. + */ +public class Limit { + + public static final Limit NO_LIMIT = new Limit(0, 0); + + private final int offset; + private final int count; + + public Limit(int offset, int count) { + this.offset = offset; + this.count = count; + } + + public void addArgs(List args) { + if (count == 0) { + return; + } + args.add("LIMIT"); + args.add(Integer.toString(offset)); + args.add(Integer.toString(count)); + } + + public List getArgs() { + if (count == 0) { + return Collections.emptyList(); + } + List ll = new ArrayList<>(3); + addArgs(ll); + return ll; + } +} diff --git a/src/main/java/redis/clients/jedis/search/aggr/Reducer.java b/src/main/java/redis/clients/jedis/search/aggr/Reducer.java new file mode 100644 index 0000000000..858fc0f7c2 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/Reducer.java @@ -0,0 +1,71 @@ +package redis.clients.jedis.search.aggr; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by mnunberg on 2/22/18. + * + * This class is normally received via one of the subclasses or via Reducers + */ +public abstract class Reducer { + + private String alias; + private final String field; + + protected Reducer(String field) { + this.field = field; + this.alias = null; + } + + protected Reducer() { + this(null); + } + + protected List getOwnArgs() { + if (field == null) { + return Collections.emptyList(); + } + List ret = new ArrayList<>(); + ret.add(field); + return ret; + } + + /** + * @return The name of the reducer + */ + public abstract String getName(); + + public final String getAlias() { + return alias; + } + + public final Reducer setAlias(String alias) { + this.alias = alias; + return this; + } + + public final Reducer as(String alias) { + return setAlias(alias); + } + + public final Reducer setAliasAsField() { + if (field == null || field.isEmpty()) { + throw new IllegalArgumentException("Cannot set to field name since no field exists"); + } + return setAlias(field); + } + + public void addArgs(List args) { + List ownArgs = getOwnArgs(); + args.add(Integer.toString(ownArgs.size())); + args.addAll(ownArgs); + } + + public final List getArgs() { + List args = new ArrayList<>(); + addArgs(args); + return args; + } +} diff --git a/src/main/java/redis/clients/jedis/search/aggr/Row.java b/src/main/java/redis/clients/jedis/search/aggr/Row.java new file mode 100644 index 0000000000..b7dbe2601d --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/Row.java @@ -0,0 +1,42 @@ +package redis.clients.jedis.search.aggr; + +import java.util.Map; + +/** + * Created by mnunberg on 5/17/18. + * + * Row in aggregation result-set + */ +public class Row { + + private final Map fields; + + public Row(Map fields) { + this.fields = fields; + } + + public boolean containsKey(String key) { + return fields.containsKey(key); + } + + public String getString(String key) { + if (!containsKey(key)) { + return ""; + } + return new String((byte[]) fields.get(key)); + } + + public long getLong(String key) { + if (!containsKey(key)) { + return 0; + } + return Long.parseLong(getString(key)); + } + + public double getDouble(String key) { + if (!containsKey(key)) { + return 0; + } + return Double.parseDouble(getString(key)); + } +} diff --git a/src/main/java/redis/clients/jedis/search/aggr/SortedField.java b/src/main/java/redis/clients/jedis/search/aggr/SortedField.java new file mode 100644 index 0000000000..4270d2f60d --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/aggr/SortedField.java @@ -0,0 +1,35 @@ +package redis.clients.jedis.search.aggr; + +/** + * Created by mnunberg on 2/22/18. + */ +public class SortedField { + + public enum SortOrder { + ASC, DESC + } + + private final String fieldName; + private final SortOrder sortOrder; + + public SortedField(String fieldName, SortOrder order) { + this.fieldName = fieldName; + this.sortOrder = order; + } + + public final String getOrder() { + return sortOrder.toString(); + } + + public final String getField() { + return fieldName; + } + + public static SortedField asc(String field) { + return new SortedField(field, SortOrder.ASC); + } + + public static SortedField desc(String field) { + return new SortedField(field, SortOrder.DESC); + } +}