From c54734a6bdc3b89910dc9b83433f571416d6d912 Mon Sep 17 00:00:00 2001 From: angelo147 <46458000+angelo147@users.noreply.github.com> Date: Sun, 7 Aug 2022 00:50:18 +0300 Subject: [PATCH 1/4] JRediSearch QueryBuilder implementation --- .../redis/clients/jedis/args/GeoUnit.java | 11 +- .../search/querybuilder/DisjunctNode.java | 23 +++ .../querybuilder/DisjunctUnionNode.java | 16 ++ .../search/querybuilder/DoubleRangeValue.java | 37 +++++ .../jedis/search/querybuilder/GeoValue.java | 36 +++++ .../search/querybuilder/IntersectNode.java | 13 ++ .../search/querybuilder/LongRangeValue.java | 39 +++++ .../jedis/search/querybuilder/Node.java | 39 +++++ .../search/querybuilder/OptionalNode.java | 21 +++ .../search/querybuilder/QueryBuilder.java | 146 ++++++++++++++++++ .../jedis/search/querybuilder/QueryNode.java | 82 ++++++++++ .../jedis/search/querybuilder/RangeValue.java | 37 +++++ .../jedis/search/querybuilder/UnionNode.java | 11 ++ .../jedis/search/querybuilder/Value.java | 12 ++ .../jedis/search/querybuilder/ValueNode.java | 81 ++++++++++ .../jedis/search/querybuilder/Values.java | 99 ++++++++++++ .../modules/search/QueryBuilderTest.java | 109 +++++++++++++ 17 files changed, 810 insertions(+), 2 deletions(-) create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/IntersectNode.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/Node.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/UnionNode.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/Value.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java create mode 100644 src/main/java/redis/clients/jedis/search/querybuilder/Values.java create mode 100644 src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java diff --git a/src/main/java/redis/clients/jedis/args/GeoUnit.java b/src/main/java/redis/clients/jedis/args/GeoUnit.java index 61dae2ac6e..cede7e83bd 100644 --- a/src/main/java/redis/clients/jedis/args/GeoUnit.java +++ b/src/main/java/redis/clients/jedis/args/GeoUnit.java @@ -8,13 +8,20 @@ public enum GeoUnit implements Rawable { M, KM, MI, FT; private final byte[] raw; + private final String unit; - private GeoUnit() { - raw = SafeEncoder.encode(name().toLowerCase(Locale.ENGLISH)); + GeoUnit() { + unit = name().toLowerCase(Locale.ENGLISH); + raw = SafeEncoder.encode(unit); } @Override public byte[] getRaw() { return raw; } + + @Override + public String toString() { + return unit; + } } diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java new file mode 100644 index 0000000000..d75bb2a6fe --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java @@ -0,0 +1,23 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * A disjunct node. evaluates to true if any of its children are false. Conversely, this node evaluates to false + * only iff all of its children are true, making it the exact inverse of {@link IntersectNode} + * + * In RS, it looks like: + * + * {@code -(@f1:v1 @f2:v2)} + * + * @see DisjunctUnionNode which evalutes to true if all its children are false. + */ +public class DisjunctNode extends IntersectNode { + @Override + public String toString(ParenMode mode) { + String ret = super.toString(ParenMode.NEVER); + if (shouldUseParens(mode)) { + return "-(" + ret + ")"; + } else { + return "-" + ret; + } + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java new file mode 100644 index 0000000000..6fc4ea538a --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java @@ -0,0 +1,16 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * A disjunct union node is the inverse of a {@link UnionNode}. It evaluates to true only iff all its + * children are false. Conversely, it evaluates to false if any of its children are true. + * + * As an RS query it looks like {@code -(@f1:v1|@f2:v2)} + * + * @see DisjunctNode which evaluates to true if any of its children are false. + */ +public class DisjunctUnionNode extends DisjunctNode { + @Override + protected String getJoinString() { + return "|"; + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java new file mode 100644 index 0000000000..fdd3e07d01 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java @@ -0,0 +1,37 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * @author mnunberg on 2/23/18. + */ +public class DoubleRangeValue extends RangeValue { + private final double from; + private final double to; + + private static void appendNum(StringBuilder sb, double n, boolean inclusive) { + if (!inclusive) { + sb.append("("); + } + if (n == Double.NEGATIVE_INFINITY) { + sb.append("-inf"); + } else if (n == Double.POSITIVE_INFINITY) { + sb.append("inf"); + } else { + sb.append(n); + } + } + + public DoubleRangeValue(double from, double to) { + this.from = from; + this.to = to; + } + + @Override + protected void appendFrom(StringBuilder sb, boolean inclusive) { + appendNum(sb, from, inclusive); + } + + @Override + protected void appendTo(StringBuilder sb, boolean inclusive) { + appendNum(sb, to, inclusive); + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java new file mode 100644 index 0000000000..c2235a1689 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java @@ -0,0 +1,36 @@ +package redis.clients.jedis.search.querybuilder; + +import redis.clients.jedis.args.GeoUnit; + +/** + * Created by mnunberg on 2/23/18. + */ +public class GeoValue extends Value { + + private final String unit; + private final double lon; + private final double lat; + private final double radius; + + public GeoValue(double lon, double lat, double radius, GeoUnit unit) { + this.lon = lon; + this.lat = lat; + this.radius = radius; + this.unit = unit.toString(); + } + + @Override + public String toString() { + return new StringBuilder("[") + .append(lon).append(" ") + .append(lat).append(" ") + .append(radius).append(" ") + .append(unit) + .append("]").toString(); + } + + @Override + public boolean isCombinable() { + return false; + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/IntersectNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/IntersectNode.java new file mode 100644 index 0000000000..979618647f --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/IntersectNode.java @@ -0,0 +1,13 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * The intersection node evaluates to true if any of its children are true. + * + * In RS: {@code @f1:v1 @f2:v2} + */ +public class IntersectNode extends QueryNode { + @Override + protected String getJoinString() { + return " "; + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java new file mode 100644 index 0000000000..8ff57926a2 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java @@ -0,0 +1,39 @@ +package redis.clients.jedis.search.querybuilder; + +public class LongRangeValue extends RangeValue { + private final long from; + private final long to; + + @Override + public boolean isCombinable() { + return false; + } + + private static void appendNum(StringBuilder sb, long n, boolean inclusive) { + if (!inclusive) { + sb.append("("); + } + if (n == Long.MIN_VALUE) { + sb.append("-inf"); + } else if (n == Long.MAX_VALUE) { + sb.append("inf"); + } else { + sb.append(Long.toString(n)); + } + } + + public LongRangeValue(long from, long to) { + this.from = from; + this.to = to; + } + + @Override + protected void appendFrom(StringBuilder sb, boolean inclusive) { + appendNum(sb, from, inclusive); + } + + @Override + protected void appendTo(StringBuilder sb, boolean inclusive) { + appendNum(sb, to, inclusive); + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/Node.java b/src/main/java/redis/clients/jedis/search/querybuilder/Node.java new file mode 100644 index 0000000000..c94e658205 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/Node.java @@ -0,0 +1,39 @@ +package redis.clients.jedis.search.querybuilder; + +import redis.clients.jedis.search.Query; + +/** + * Created by mnunberg on 2/23/18. + * + * Base node interface + */ +public interface Node { + enum ParenMode { + /** Always encapsulate */ + ALWAYS, + + /** + * Never encapsulate. Note that this may be ignored if parentheses are semantically required (e.g. + * {@code @foo:(val1|val2)}. However something like {@code @foo:v1 @bar:v2} need not be parenthesized. + */ + NEVER, + + /** + * Determine encapsulation based on number of children. If the node only has one child, it is not + * parenthesized, if it has more than one child, it is parenthesized */ + DEFAULT + } + + /** + * Returns the string form of this node. + * @param mode Whether the string should be encapsulated in parentheses {@code (...)} + * @return The string query. + */ + String toString(ParenMode mode); + + /** + * Returns the string form of this node. This may be passed to {@link redis.clients.jedis.UnifiedJedis#ftSearch(String, Query)} + * @return The query string. + */ + String toString(); +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java new file mode 100644 index 0000000000..4ba0778161 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java @@ -0,0 +1,21 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * Created by mnunberg on 2/23/18. + * + * The optional node affects scoring and ordering. If it evaluates to true, the result is ranked + * higher. It is helpful to combine it with a {@link UnionNode} to rank a document higher if it meets + * one of several criteria. + * + * In RS: {@code ~(@lang:en @country:us)}. + */ +public class OptionalNode extends IntersectNode { + @Override + public String toString(ParenMode mode) { + String ret = super.toString(ParenMode.NEVER); + if (shouldUseParens(mode)) { + return "~(" + ret + ")"; + } + return "~" + ret; + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java b/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java new file mode 100644 index 0000000000..fece3fc0a9 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java @@ -0,0 +1,146 @@ +package redis.clients.jedis.search.querybuilder; + +import java.util.Arrays; + +import static redis.clients.jedis.search.querybuilder.Values.value; + +/** + * Created by mnunberg on 2/23/18. + * + * This class contains methods to construct query nodes. These query nodes can be added to parent query + * nodes (building a chain) or used as the root query node. + * + * You can use
import static
for these helper methods. + */ +public class QueryBuilder { + private QueryBuilder() { + } + + /** + * Create a new intersection node with child nodes. An intersection node is true if all its children + * are also true + * @param n sub-condition to add + * @return The node + */ + public static QueryNode intersect(Node ...n) { + return new IntersectNode().add(n); + } + + /** + * Create a new intersection node with a field-value pair. + * @param field The field that should contain this value. If this value is empty, then any field + * will be checked. + * @param values Value to check for. The node will be true only if the field (or any field) + * contains all of the values + * @return The node + */ + public static QueryNode intersect(String field, Value ...values) { + return intersect().add(field, values); + } + + /** + * Helper method to create a new intersection node with a string value. + * @param field The field to check. If left null or empty, all fields will be checked. + * @param stringValue The value to check + * @return The node + */ + public static QueryNode intersect(String field, String stringValue) { + return intersect(field, value(stringValue)); + } + + /** + * Create a union node. Union nodes evaluate to true if any of its children are true + * @param n Child node + * @return The union node + */ + public static QueryNode union(Node ...n) { + return new UnionNode().add(n); + } + + /** + * Create a union node which can match an one or more values + * @param field Field to check. If empty, all fields are checked + * @param values Values to search for. The node evaluates to true if {@code field} matches + * any of the values + * @return The union node + */ + public static QueryNode union(String field, Value ...values) { + return union().add(field, values); + } + + /** + * Convenience method to match one or more strings. This is equivalent to + * {@code union(field, value(v1), value(v2), value(v3)) ...} + * @param field Field to match + * @param values Strings to check for + * @return The union node + */ + public static QueryNode union(String field, String ...values) { + return union(field, (Value[]) Arrays.stream(values).map(Values::value).toArray()); + } + + /** + * Create a disjunct node. Disjunct nodes are true iff any of its children are not true. + * Conversely, this node evaluates to false if all its children are true. + * @param n Child nodes to add + * @return The disjunct node + */ + public static QueryNode disjunct(Node ...n) { + return new DisjunctNode().add(n); + } + + /** + * Create a disjunct node using one or more values. The node will evaluate to true iff the field does not + * match any of the values. + * + * @param field Field to check for (empty or null for any field) + * @param values The values to check for + * @return The node + */ + public static QueryNode disjunct(String field, Value ...values) { + return disjunct().add(field, values); + } + + /** + * Create a disjunct node using one or more values. The node will evaluate to true iff the field does not + * match any of the values. + * + * @param field Field to check for (empty or null for any field) + * @param values The values to check for + * @return The node + */ + public static QueryNode disjunct(String field, String ...values) { + return disjunct(field, (Value[]) Arrays.stream(values).map(Values::value).toArray()); + } + + /** + * Create a disjunct union node. This node evaluates to true if all of its children are not true. + * Conversely, this node evaluates as false if any of its children are true. + * @param n + * @return The node + */ + public static QueryNode disjunctUnion(Node ...n) { + return new DisjunctUnionNode().add(n); + } + + public static QueryNode disjunctUnion(String field, Value ...values) { + return disjunctUnion().add(field, values); + } + + public static QueryNode disjunctUnion(String field, String ...values) { + return disjunctUnion(field, (Value[]) Arrays.stream(values).map(Values::value).toArray()); + } + + /** + * Create an optional node. Optional nodes do not affect which results are returned but they influence + * ordering and scoring. + * @param n The node to evaluate as optional + * @return The new node + */ + public static QueryNode optional(Node ...n) { + return new OptionalNode().add(n); + } + public static QueryNode optional(String field, Value ...values) { + return optional().add(field, values); + } +} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java new file mode 100644 index 0000000000..5c8f35b435 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java @@ -0,0 +1,82 @@ +package redis.clients.jedis.search.querybuilder; + +import java.util.*; + +public abstract class QueryNode implements Node { + private final List children = new ArrayList<>(); + protected abstract String getJoinString(); + + /** + * Add a match criteria to this node + * @param field The field to check. If null or empty, then any field is checked + * @param values Values to check for. + * @return The current node, for chaining. + */ + public QueryNode add(String field, Value ...values) { + children.add(new ValueNode(field, getJoinString(), values)); + return this; + } + + /** + * Convenience method to add a list of string values + * @param field Field to check for + * @param values One or more string values. + * @return The current node, for chaining. + */ + public QueryNode add(String field, String ...values) { + children.add(new ValueNode(field, getJoinString(), values)); + return this; + } + + /** + * Add a list of values from a collection + * @param field The field to check + * @param values Collection of values to match + * @return The current node for chaining. + */ + public QueryNode add(String field, Collection values) { + return add(field, values.toArray(new Value[0])); + } + + /** + * Add children nodes to this node. + * @param nodes Children nodes to add + * @return The current node, for chaining. + */ + public QueryNode add(Node ...nodes) { + children.addAll(Arrays.asList(nodes)); + return this; + } + + protected boolean shouldUseParens(ParenMode mode) { + if (mode == ParenMode.ALWAYS) { + return true; + } + if (mode == ParenMode.NEVER) { + return false; + } + return children.size() > 1; + } + + @Override + public String toString(ParenMode parenMode) { + StringBuilder sb = new StringBuilder(); + StringJoiner sj = new StringJoiner(getJoinString()); + if (shouldUseParens(parenMode)) { + sb.append('('); + } + for (Node n : children) { + sj.add(n.toString(parenMode)); + } + sb.append(sj.toString()); + if (shouldUseParens(parenMode)) { + sb.append(')'); + } + return sb.toString(); + } + + @Override + public String toString() { + return toString(ParenMode.DEFAULT); + } +} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java new file mode 100644 index 0000000000..dc684a822e --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java @@ -0,0 +1,37 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * @author mnunberg on 2/23/18. + */ +public abstract class RangeValue extends Value { + private boolean inclusiveMin = true; + private boolean inclusiveMax = true; + + @Override + public boolean isCombinable() { + return false; + } + + protected abstract void appendFrom(StringBuilder sb, boolean inclusive); + protected abstract void appendTo(StringBuilder sb, boolean inclusive); + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + appendFrom(sb, inclusiveMin); + sb.append(' '); + appendTo(sb, inclusiveMax); + sb.append(']'); + return sb.toString(); + } + + public RangeValue inclusiveMin(boolean val) { + inclusiveMin = val; + return this; + } + public RangeValue inclusiveMax(boolean val) { + inclusiveMax = val; + return this; + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/UnionNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/UnionNode.java new file mode 100644 index 0000000000..8066df1bc6 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/UnionNode.java @@ -0,0 +1,11 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * Created by mnunberg on 2/23/18. + */ +public class UnionNode extends QueryNode { + @Override + protected String getJoinString() { + return "|"; + } +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/Value.java b/src/main/java/redis/clients/jedis/search/querybuilder/Value.java new file mode 100644 index 0000000000..4508f02e89 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/Value.java @@ -0,0 +1,12 @@ +package redis.clients.jedis.search.querybuilder; + +/** + * Created by mnunberg on 2/23/18. + */ +public abstract class Value { + public boolean isCombinable() { + return false; + } + + public abstract String toString(); +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java new file mode 100644 index 0000000000..4e1ea1157c --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java @@ -0,0 +1,81 @@ +package redis.clients.jedis.search.querybuilder; + +import java.util.StringJoiner; + +/** + * Created by mnunberg on 2/23/18. + */ +public class ValueNode implements Node { + private final Value[] values; + private final String field; + private final String joinString; + + public ValueNode(String field, String joinstr, Value... values) { + this.field = field; + this.values = values; + this.joinString = joinstr; + } + + private static Value[] fromStrings(String[] values) { + Value[] objs = new Value[values.length]; + for (int i = 0; i < values.length; i++) { + objs[i] = Values.value(values[i]); + } + return objs; + } + + public ValueNode(String field, String joinstr, String ...values) { + this(field, joinstr, fromStrings(values)); + } + + private String formatField() { + if (field == null || field.isEmpty()) { + return ""; + } + return '@' + field + ':'; + } + + private String toStringCombinable(ParenMode mode) { + StringBuilder sb = new StringBuilder(formatField()); + if (values.length > 1 || mode == ParenMode.ALWAYS) { + sb.append('('); + } + StringJoiner sj = new StringJoiner(joinString); + for (Value v : values) { + sj.add(v.toString()); + } + sb.append(sj.toString()); + if (values.length > 1 || mode == ParenMode.ALWAYS) { + sb.append(')'); + } + return sb.toString(); + } + + private String toStringDefault(ParenMode mode) { + boolean useParen = mode == ParenMode.ALWAYS; + if (!useParen) { + useParen = mode != ParenMode.NEVER && values.length > 1; + } + StringBuilder sb = new StringBuilder(); + if (useParen) { + sb.append('('); + } + StringJoiner sj = new StringJoiner(joinString); + for (Value v : values) { + sj.add(formatField() + v.toString()); + } + sb.append(sj.toString()); + if (useParen) { + sb.append(')'); + } + return sb.toString(); + } + + @Override + public String toString(ParenMode mode) { + if (values[0].isCombinable()) { + return toStringCombinable(mode); + } + return toStringDefault(mode); + } +} \ No newline at end of file diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/Values.java b/src/main/java/redis/clients/jedis/search/querybuilder/Values.java new file mode 100644 index 0000000000..8bc2e247e2 --- /dev/null +++ b/src/main/java/redis/clients/jedis/search/querybuilder/Values.java @@ -0,0 +1,99 @@ +package redis.clients.jedis.search.querybuilder; + +import redis.clients.jedis.GeoCoordinate; +import redis.clients.jedis.args.GeoUnit; + +import java.util.StringJoiner; + +/** + * Created by mnunberg on 2/23/18. + */ +public class Values { + private Values() { + } + + private abstract static class ScalableValue extends Value { + @Override + public boolean isCombinable() { + return true; + } + } + + public static Value value(String s) { + return new ScalableValue() { + @Override + public String toString() { + return s; + } + }; + } + + public static GeoValue geo(GeoCoordinate coord, double radius, GeoUnit unit) { + return new GeoValue(coord.getLongitude(), coord.getLatitude(), radius, unit); + } + + public static RangeValue between(double from, double to) { + return new DoubleRangeValue(from, to); + } + + public static RangeValue between(int from, int to) { + return new LongRangeValue(from, to); + } + + public static RangeValue eq(double d) { + return new DoubleRangeValue(d, d); + } + + public static RangeValue eq(int i) { + return new LongRangeValue(i, i); + } + + public static RangeValue lt(double d) { + return new DoubleRangeValue(Double.NEGATIVE_INFINITY, d).inclusiveMax(false); + } + + public static RangeValue lt(int d) { + return new LongRangeValue(Long.MIN_VALUE, d).inclusiveMax(false); + } + + public static RangeValue gt(double d) { + return new DoubleRangeValue(d, Double.POSITIVE_INFINITY).inclusiveMin(false); + } + + public static RangeValue gt(int d) { + return new LongRangeValue(d, Long.MAX_VALUE).inclusiveMin(false); + } + + public static RangeValue le(double d) { + return lt(d).inclusiveMax(true); + } + + public static RangeValue le(int d) { + return lt(d).inclusiveMax(true); + } + + public static RangeValue ge(double d) { + return gt(d).inclusiveMin(true); + } + + public static RangeValue ge(int d) { + return gt(d).inclusiveMin(true); + } + + + public static Value tags(String ...tags) { + if (tags.length == 0) { + throw new IllegalArgumentException("Must have at least one tag"); + } + StringJoiner sj = new StringJoiner(" | "); + for (String s : tags) { + sj.add(s); + } + return new Value() { + @Override + public String toString() { + return "{" + sj.toString() + "}"; + } + }; + } +} diff --git a/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java new file mode 100644 index 0000000000..c9e92f361b --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java @@ -0,0 +1,109 @@ +package redis.clients.jedis.modules.search; + +import org.junit.Test; +import static org.junit.Assert.*; + +import redis.clients.jedis.GeoCoordinate; +import redis.clients.jedis.args.GeoUnit; +import redis.clients.jedis.search.querybuilder.Node; +import redis.clients.jedis.search.querybuilder.Value; +import redis.clients.jedis.search.querybuilder.Values; + +import static redis.clients.jedis.search.querybuilder.Values.*; +import static redis.clients.jedis.search.querybuilder.QueryBuilder.*; +import java.util.Arrays; + + +/** + * Created by mnunberg on 2/23/18. + */ +public class QueryBuilderTest { + @Test + public void testTag() { + Value v = tags("foo"); + assertEquals("{foo}", v.toString()); + v = tags("foo", "bar"); + assertEquals("{foo | bar}", v.toString()); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptyTag() { + tags(); + } + + @Test + public void testRange() { + Value v = between(1, 10); + assertEquals("[1 10]", v.toString()); + v = between(1, 10).inclusiveMax(false); + assertEquals("[1 (10]", v.toString()); + v = between(1, 10).inclusiveMin(false); + assertEquals("[(1 10]", v.toString()); + + v = between(1.0, 10.1); + assertEquals("[1.0 10.1]", v.toString()); + v = between(-1.0, 10.1).inclusiveMax(false); + assertEquals("[-1.0 (10.1]", v.toString()); + v = between(-1.1, 150.61).inclusiveMin(false); + assertEquals("[(-1.1 150.61]", v.toString()); + + // le, gt, etc. + // le, gt, etc. + assertEquals("[42 42]", eq(42).toString()); + assertEquals("[-inf (42]", lt(42).toString()); + assertEquals("[-inf 42]", le(42).toString()); + assertEquals("[(-42 inf]", gt(-42).toString()); + assertEquals("[42 inf]", ge(42).toString()); + + assertEquals("[42.0 42.0]", eq(42.0).toString()); + assertEquals("[-inf (42.0]", lt(42.0).toString()); + assertEquals("[-inf 42.0]", le(42.0).toString()); + assertEquals("[(42.0 inf]", gt(42.0).toString()); + assertEquals("[42.0 inf]", ge(42.0).toString()); + + assertEquals("[(1587058030 inf]", gt(1587058030).toString()); + + // string value + assertEquals("s", value("s").toString()); + + // Geo value + assertEquals("[1.0 2.0 3.0 km]", + geo(new GeoCoordinate(1.0, 2.0), 3.0, GeoUnit.KM).toString()); + } + + @Test + public void testIntersectionBasic() { + Node n = intersect().add("name", "mark"); + assertEquals("@name:mark", n.toString()); + + n = intersect().add("name", "mark", "dvir"); + assertEquals("@name:(mark dvir)", n.toString()); + + n = intersect().add("name", Arrays.asList(Values.value("mark"), Values.value("shay"))); + assertEquals("@name:(mark shay)", n.toString()); + + n = intersect("name", "meir"); + assertEquals("@name:meir", n.toString()); + + n = intersect("name", Values.value("meir"), Values.value("rafi")); + assertEquals("@name:(meir rafi)", n.toString()); + } + + @Test + public void testIntersectionNested() { + Node n = intersect(). + add(union("name", value("mark"), value("dvir"))). + add("time", between(100, 200)). + add(disjunct("created", lt(1000))); + assertEquals("(@name:(mark|dvir) @time:[100 200] -@created:[-inf (1000])", n.toString()); + } + + @Test + public void testOptional() { + Node n = optional("name", tags("foo", "bar")); + assertEquals("~@name:{foo | bar}", n.toString()); + + n = optional(n, n); + assertEquals("~(~@name:{foo | bar} ~@name:{foo | bar})", n.toString()); + } +} From 85705e4e41c2658a3875a373df554f27ca094e67 Mon Sep 17 00:00:00 2001 From: angelo147 <46458000+angelo147@users.noreply.github.com> Date: Tue, 9 Aug 2022 00:16:11 +0300 Subject: [PATCH 2/4] Update GeoValue.java --- .../redis/clients/jedis/search/querybuilder/GeoValue.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java index c2235a1689..54d1c6e574 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java @@ -21,12 +21,7 @@ public GeoValue(double lon, double lat, double radius, GeoUnit unit) { @Override public String toString() { - return new StringBuilder("[") - .append(lon).append(" ") - .append(lat).append(" ") - .append(radius).append(" ") - .append(unit) - .append("]").toString(); + return "[" + lon + " " + lat + " " + radius + " " + unit + "]"; } @Override From a506a7c3c7b9c5405a8fe20aa65297c9f3dd0d76 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 8 Sep 2022 21:38:01 +0600 Subject: [PATCH 3/4] Address review comments and format --- .../redis/clients/jedis/args/GeoUnit.java | 11 +-- .../search/querybuilder/DisjunctNode.java | 13 +-- .../querybuilder/DisjunctUnionNode.java | 5 +- .../search/querybuilder/DoubleRangeValue.java | 1 + .../jedis/search/querybuilder/GeoValue.java | 8 +- .../search/querybuilder/LongRangeValue.java | 1 + .../jedis/search/querybuilder/Node.java | 29 ++++-- .../search/querybuilder/OptionalNode.java | 11 +-- .../search/querybuilder/QueryBuilder.java | 88 +++++++++++-------- .../jedis/search/querybuilder/QueryNode.java | 34 ++++--- .../jedis/search/querybuilder/RangeValue.java | 3 + .../jedis/search/querybuilder/Value.java | 1 + .../jedis/search/querybuilder/ValueNode.java | 19 ++-- .../jedis/search/querybuilder/Values.java | 4 +- .../modules/search/QueryBuilderTest.java | 12 +-- 15 files changed, 138 insertions(+), 102 deletions(-) diff --git a/src/main/java/redis/clients/jedis/args/GeoUnit.java b/src/main/java/redis/clients/jedis/args/GeoUnit.java index cede7e83bd..61dae2ac6e 100644 --- a/src/main/java/redis/clients/jedis/args/GeoUnit.java +++ b/src/main/java/redis/clients/jedis/args/GeoUnit.java @@ -8,20 +8,13 @@ public enum GeoUnit implements Rawable { M, KM, MI, FT; private final byte[] raw; - private final String unit; - GeoUnit() { - unit = name().toLowerCase(Locale.ENGLISH); - raw = SafeEncoder.encode(unit); + private GeoUnit() { + raw = SafeEncoder.encode(name().toLowerCase(Locale.ENGLISH)); } @Override public byte[] getRaw() { return raw; } - - @Override - public String toString() { - return unit; - } } diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java index d75bb2a6fe..d982072416 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctNode.java @@ -1,8 +1,9 @@ package redis.clients.jedis.search.querybuilder; /** - * A disjunct node. evaluates to true if any of its children are false. Conversely, this node evaluates to false - * only iff all of its children are true, making it the exact inverse of {@link IntersectNode} + * A disjunct node. evaluates to true if any of its children are false. Conversely, this node + * evaluates to false only iff all of its children are true, making it the exact inverse of + * {@link IntersectNode} * * In RS, it looks like: * @@ -12,10 +13,10 @@ */ public class DisjunctNode extends IntersectNode { @Override - public String toString(ParenMode mode) { - String ret = super.toString(ParenMode.NEVER); - if (shouldUseParens(mode)) { - return "-(" + ret + ")"; + public String toString(Parenthesize mode) { + String ret = super.toString(Parenthesize.NEVER); + if (shouldParenthesize(mode)) { + return "-(" + ret + ")"; } else { return "-" + ret; } diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java index 6fc4ea538a..36be78bf31 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/DisjunctUnionNode.java @@ -1,8 +1,9 @@ package redis.clients.jedis.search.querybuilder; /** - * A disjunct union node is the inverse of a {@link UnionNode}. It evaluates to true only iff all its - * children are false. Conversely, it evaluates to false if any of its children are true. + * A disjunct union node is the inverse of a {@link UnionNode}. It evaluates to true only iff + * all its children are false. Conversely, it evaluates to false if any of its + * children are true. * * As an RS query it looks like {@code -(@f1:v1|@f2:v2)} * diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java index fdd3e07d01..75fc3843be 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/DoubleRangeValue.java @@ -4,6 +4,7 @@ * @author mnunberg on 2/23/18. */ public class DoubleRangeValue extends RangeValue { + private final double from; private final double to; diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java index 54d1c6e574..3c6727b217 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/GeoValue.java @@ -1,5 +1,6 @@ package redis.clients.jedis.search.querybuilder; +import java.util.Locale; import redis.clients.jedis.args.GeoUnit; /** @@ -7,7 +8,7 @@ */ public class GeoValue extends Value { - private final String unit; + private final GeoUnit unit; private final double lon; private final double lat; private final double radius; @@ -16,12 +17,13 @@ public GeoValue(double lon, double lat, double radius, GeoUnit unit) { this.lon = lon; this.lat = lat; this.radius = radius; - this.unit = unit.toString(); + this.unit = unit; } @Override public String toString() { - return "[" + lon + " " + lat + " " + radius + " " + unit + "]"; + return "[" + lon + " " + lat + " " + radius + + " " + unit.name().toLowerCase(Locale.ENGLISH) + "]"; } @Override diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java index 8ff57926a2..ffa072aa5a 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/LongRangeValue.java @@ -1,6 +1,7 @@ package redis.clients.jedis.search.querybuilder; public class LongRangeValue extends RangeValue { + private final long from; private final long to; diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/Node.java b/src/main/java/redis/clients/jedis/search/querybuilder/Node.java index c94e658205..013ac79b6f 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/Node.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/Node.java @@ -8,32 +8,43 @@ * Base node interface */ public interface Node { - enum ParenMode { - /** Always encapsulate */ + + enum Parenthesize { + + /** + * Always encapsulate + */ ALWAYS, /** - * Never encapsulate. Note that this may be ignored if parentheses are semantically required (e.g. - * {@code @foo:(val1|val2)}. However something like {@code @foo:v1 @bar:v2} need not be parenthesized. + * Never encapsulate. Note that this may be ignored if parentheses are semantically required + * (e.g. {@code @foo:(val1|val2)}. However something like {@code @foo:v1 @bar:v2} need not be + * parenthesized. */ - NEVER, + NEVER, /** - * Determine encapsulation based on number of children. If the node only has one child, it is not - * parenthesized, if it has more than one child, it is parenthesized */ + * Determine encapsulation based on number of children. If the node only has one child, it is + * not parenthesized, if it has more than one child, it is parenthesized + */ + DEFAULT } /** * Returns the string form of this node. + * * @param mode Whether the string should be encapsulated in parentheses {@code (...)} * @return The string query. */ - String toString(ParenMode mode); + String toString(Parenthesize mode); /** - * Returns the string form of this node. This may be passed to {@link redis.clients.jedis.UnifiedJedis#ftSearch(String, Query)} + * Returns the string form of this node. This may be passed to + * {@link redis.clients.jedis.UnifiedJedis#ftSearch(String, Query)} + * * @return The query string. */ + @Override String toString(); } diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java index 4ba0778161..7a9c728a05 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/OptionalNode.java @@ -4,16 +4,17 @@ * Created by mnunberg on 2/23/18. * * The optional node affects scoring and ordering. If it evaluates to true, the result is ranked - * higher. It is helpful to combine it with a {@link UnionNode} to rank a document higher if it meets - * one of several criteria. + * higher. It is helpful to combine it with a {@link UnionNode} to rank a document higher if it + * meets one of several criteria. * * In RS: {@code ~(@lang:en @country:us)}. */ public class OptionalNode extends IntersectNode { + @Override - public String toString(ParenMode mode) { - String ret = super.toString(ParenMode.NEVER); - if (shouldUseParens(mode)) { + public String toString(Parenthesize mode) { + String ret = super.toString(Parenthesize.NEVER); + if (shouldParenthesize(mode)) { return "~(" + ret + ")"; } return "~" + ret; diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java b/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java index fece3fc0a9..e222629d91 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java @@ -7,39 +7,42 @@ /** * Created by mnunberg on 2/23/18. * - * This class contains methods to construct query nodes. These query nodes can be added to parent query - * nodes (building a chain) or used as the root query node. - * - * You can use
import static
for these helper methods. + * This class contains methods to construct query nodes. These query nodes can be added to parent + * query nodes (building a chain) or used as the root query node. */ public class QueryBuilder { + private QueryBuilder() { + throw new InstantiationError("Must not instantiate this class"); } /** - * Create a new intersection node with child nodes. An intersection node is true if all its children - * are also true + * Create a new intersection node with child nodes. An intersection node is true if all its + * children are also true + * * @param n sub-condition to add * @return The node */ - public static QueryNode intersect(Node ...n) { + public static QueryNode intersect(Node... n) { return new IntersectNode().add(n); } /** * Create a new intersection node with a field-value pair. + * * @param field The field that should contain this value. If this value is empty, then any field - * will be checked. + * will be checked. * @param values Value to check for. The node will be true only if the field (or any field) - * contains all of the values + * contains all of the values * @return The node */ - public static QueryNode intersect(String field, Value ...values) { - return intersect().add(field, values); + public static QueryNode intersect(String field, Value... values) { + return new IntersectNode().add(field, values); } /** * Helper method to create a new intersection node with a string value. + * * @param field The field to check. If left null or empty, all fields will be checked. * @param stringValue The value to check * @return The node @@ -50,97 +53,104 @@ public static QueryNode intersect(String field, String stringValue) { /** * Create a union node. Union nodes evaluate to true if any of its children are true + * * @param n Child node * @return The union node */ - public static QueryNode union(Node ...n) { + public static QueryNode union(Node... n) { return new UnionNode().add(n); } /** * Create a union node which can match an one or more values + * * @param field Field to check. If empty, all fields are checked - * @param values Values to search for. The node evaluates to true if {@code field} matches - * any of the values + * @param values Values to search for. The node evaluates to true if {@code field} matches any of + * the values * @return The union node */ - public static QueryNode union(String field, Value ...values) { - return union().add(field, values); + public static QueryNode union(String field, Value... values) { + return new UnionNode().add(field, values); } /** * Convenience method to match one or more strings. This is equivalent to * {@code union(field, value(v1), value(v2), value(v3)) ...} + * * @param field Field to match * @param values Strings to check for * @return The union node */ - public static QueryNode union(String field, String ...values) { + public static QueryNode union(String field, String... values) { return union(field, (Value[]) Arrays.stream(values).map(Values::value).toArray()); } /** - * Create a disjunct node. Disjunct nodes are true iff any of its children are not true. - * Conversely, this node evaluates to false if all its children are true. + * Create a disjunct node. Disjunct nodes are true iff any of its children are not + * true. Conversely, this node evaluates to false if all its children are true. + * * @param n Child nodes to add * @return The disjunct node */ - public static QueryNode disjunct(Node ...n) { + public static QueryNode disjunct(Node... n) { return new DisjunctNode().add(n); } /** - * Create a disjunct node using one or more values. The node will evaluate to true iff the field does not - * match any of the values. + * Create a disjunct node using one or more values. The node will evaluate to true iff the field + * does not match any of the values. * * @param field Field to check for (empty or null for any field) * @param values The values to check for * @return The node */ - public static QueryNode disjunct(String field, Value ...values) { - return disjunct().add(field, values); + public static QueryNode disjunct(String field, Value... values) { + return new DisjunctNode().add(field, values); } /** - * Create a disjunct node using one or more values. The node will evaluate to true iff the field does not - * match any of the values. + * Create a disjunct node using one or more values. The node will evaluate to true iff the field + * does not match any of the values. * * @param field Field to check for (empty or null for any field) * @param values The values to check for * @return The node */ - public static QueryNode disjunct(String field, String ...values) { + public static QueryNode disjunct(String field, String... values) { return disjunct(field, (Value[]) Arrays.stream(values).map(Values::value).toArray()); } /** - * Create a disjunct union node. This node evaluates to true if all of its children are not true. - * Conversely, this node evaluates as false if any of its children are true. + * Create a disjunct union node. This node evaluates to true if all of its children are not + * true. Conversely, this node evaluates as false if any of its children are true. + * * @param n * @return The node */ - public static QueryNode disjunctUnion(Node ...n) { + public static QueryNode disjunctUnion(Node... n) { return new DisjunctUnionNode().add(n); } - public static QueryNode disjunctUnion(String field, Value ...values) { - return disjunctUnion().add(field, values); + public static QueryNode disjunctUnion(String field, Value... values) { + return new DisjunctUnionNode().add(field, values); } - public static QueryNode disjunctUnion(String field, String ...values) { + public static QueryNode disjunctUnion(String field, String... values) { return disjunctUnion(field, (Value[]) Arrays.stream(values).map(Values::value).toArray()); } /** - * Create an optional node. Optional nodes do not affect which results are returned but they influence - * ordering and scoring. + * Create an optional node. Optional nodes do not affect which results are returned but they + * influence ordering and scoring. + * * @param n The node to evaluate as optional * @return The new node */ - public static QueryNode optional(Node ...n) { + public static QueryNode optional(Node... n) { return new OptionalNode().add(n); } - public static QueryNode optional(String field, Value ...values) { - return optional().add(field, values); + + public static QueryNode optional(String field, Value... values) { + return new OptionalNode().add(field, values); } -} \ No newline at end of file +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java index 5c8f35b435..bc64374a5a 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/QueryNode.java @@ -1,35 +1,44 @@ package redis.clients.jedis.search.querybuilder; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.StringJoiner; public abstract class QueryNode implements Node { + private final List children = new ArrayList<>(); + protected abstract String getJoinString(); /** * Add a match criteria to this node + * * @param field The field to check. If null or empty, then any field is checked * @param values Values to check for. * @return The current node, for chaining. */ - public QueryNode add(String field, Value ...values) { + public QueryNode add(String field, Value... values) { children.add(new ValueNode(field, getJoinString(), values)); return this; } /** * Convenience method to add a list of string values + * * @param field Field to check for * @param values One or more string values. * @return The current node, for chaining. */ - public QueryNode add(String field, String ...values) { + public QueryNode add(String field, String... values) { children.add(new ValueNode(field, getJoinString(), values)); return this; } /** * Add a list of values from a collection + * * @param field The field to check * @param values Collection of values to match * @return The current node for chaining. @@ -40,36 +49,37 @@ public QueryNode add(String field, Collection values) { /** * Add children nodes to this node. + * * @param nodes Children nodes to add * @return The current node, for chaining. */ - public QueryNode add(Node ...nodes) { + public QueryNode add(Node... nodes) { children.addAll(Arrays.asList(nodes)); return this; } - protected boolean shouldUseParens(ParenMode mode) { - if (mode == ParenMode.ALWAYS) { + protected boolean shouldParenthesize(Parenthesize mode) { + if (mode == Parenthesize.ALWAYS) { return true; } - if (mode == ParenMode.NEVER) { + if (mode == Parenthesize.NEVER) { return false; } return children.size() > 1; } @Override - public String toString(ParenMode parenMode) { + public String toString(Parenthesize parenMode) { StringBuilder sb = new StringBuilder(); StringJoiner sj = new StringJoiner(getJoinString()); - if (shouldUseParens(parenMode)) { + if (shouldParenthesize(parenMode)) { sb.append('('); } for (Node n : children) { sj.add(n.toString(parenMode)); } sb.append(sj.toString()); - if (shouldUseParens(parenMode)) { + if (shouldParenthesize(parenMode)) { sb.append(')'); } return sb.toString(); @@ -77,6 +87,6 @@ public String toString(ParenMode parenMode) { @Override public String toString() { - return toString(ParenMode.DEFAULT); + return toString(Parenthesize.DEFAULT); } -} \ No newline at end of file +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java b/src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java index dc684a822e..9d05657c33 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/RangeValue.java @@ -4,6 +4,7 @@ * @author mnunberg on 2/23/18. */ public abstract class RangeValue extends Value { + private boolean inclusiveMin = true; private boolean inclusiveMax = true; @@ -13,6 +14,7 @@ public boolean isCombinable() { } protected abstract void appendFrom(StringBuilder sb, boolean inclusive); + protected abstract void appendTo(StringBuilder sb, boolean inclusive); @Override @@ -30,6 +32,7 @@ public RangeValue inclusiveMin(boolean val) { inclusiveMin = val; return this; } + public RangeValue inclusiveMax(boolean val) { inclusiveMax = val; return this; diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/Value.java b/src/main/java/redis/clients/jedis/search/querybuilder/Value.java index 4508f02e89..54e7755fa9 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/Value.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/Value.java @@ -8,5 +8,6 @@ public boolean isCombinable() { return false; } + @Override public abstract String toString(); } diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java b/src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java index 4e1ea1157c..5bcb587001 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/ValueNode.java @@ -6,6 +6,7 @@ * Created by mnunberg on 2/23/18. */ public class ValueNode implements Node { + private final Value[] values; private final String field; private final String joinString; @@ -24,7 +25,7 @@ private static Value[] fromStrings(String[] values) { return objs; } - public ValueNode(String field, String joinstr, String ...values) { + public ValueNode(String field, String joinstr, String... values) { this(field, joinstr, fromStrings(values)); } @@ -35,9 +36,9 @@ private String formatField() { return '@' + field + ':'; } - private String toStringCombinable(ParenMode mode) { + private String toStringCombinable(Parenthesize mode) { StringBuilder sb = new StringBuilder(formatField()); - if (values.length > 1 || mode == ParenMode.ALWAYS) { + if (values.length > 1 || mode == Parenthesize.ALWAYS) { sb.append('('); } StringJoiner sj = new StringJoiner(joinString); @@ -45,16 +46,16 @@ private String toStringCombinable(ParenMode mode) { sj.add(v.toString()); } sb.append(sj.toString()); - if (values.length > 1 || mode == ParenMode.ALWAYS) { + if (values.length > 1 || mode == Parenthesize.ALWAYS) { sb.append(')'); } return sb.toString(); } - private String toStringDefault(ParenMode mode) { - boolean useParen = mode == ParenMode.ALWAYS; + private String toStringDefault(Parenthesize mode) { + boolean useParen = mode == Parenthesize.ALWAYS; if (!useParen) { - useParen = mode != ParenMode.NEVER && values.length > 1; + useParen = mode != Parenthesize.NEVER && values.length > 1; } StringBuilder sb = new StringBuilder(); if (useParen) { @@ -72,10 +73,10 @@ private String toStringDefault(ParenMode mode) { } @Override - public String toString(ParenMode mode) { + public String toString(Parenthesize mode) { if (values[0].isCombinable()) { return toStringCombinable(mode); } return toStringDefault(mode); } -} \ No newline at end of file +} diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/Values.java b/src/main/java/redis/clients/jedis/search/querybuilder/Values.java index 8bc2e247e2..67256f2359 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/Values.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/Values.java @@ -10,6 +10,7 @@ */ public class Values { private Values() { + throw new InstantiationError("Must not instantiate this class"); } private abstract static class ScalableValue extends Value { @@ -80,8 +81,7 @@ public static RangeValue ge(int d) { return gt(d).inclusiveMin(true); } - - public static Value tags(String ...tags) { + public static Value tags(String... tags) { if (tags.length == 0) { throw new IllegalArgumentException("Must have at least one tag"); } diff --git a/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java index c9e92f361b..88d2cb376d 100644 --- a/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java @@ -1,7 +1,11 @@ package redis.clients.jedis.modules.search; +import static org.junit.Assert.assertEquals; +import static redis.clients.jedis.search.querybuilder.QueryBuilder.*; +import static redis.clients.jedis.search.querybuilder.Values.*; + +import java.util.Arrays; import org.junit.Test; -import static org.junit.Assert.*; import redis.clients.jedis.GeoCoordinate; import redis.clients.jedis.args.GeoUnit; @@ -9,15 +13,11 @@ import redis.clients.jedis.search.querybuilder.Value; import redis.clients.jedis.search.querybuilder.Values; -import static redis.clients.jedis.search.querybuilder.Values.*; -import static redis.clients.jedis.search.querybuilder.QueryBuilder.*; -import java.util.Arrays; - - /** * Created by mnunberg on 2/23/18. */ public class QueryBuilderTest { + @Test public void testTag() { Value v = tags("foo"); From 2426d1bd2b6c6e178444707fda24d72857212ff9 Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Thu, 8 Sep 2022 21:44:50 +0600 Subject: [PATCH 4/4] Rename to QueryBuilders --- .../querybuilder/{QueryBuilder.java => QueryBuilders.java} | 5 ++--- .../redis/clients/jedis/modules/search/QueryBuilderTest.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) rename src/main/java/redis/clients/jedis/search/querybuilder/{QueryBuilder.java => QueryBuilders.java} (98%) diff --git a/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java b/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilders.java similarity index 98% rename from src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java rename to src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilders.java index e222629d91..92576b80c5 100644 --- a/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilder.java +++ b/src/main/java/redis/clients/jedis/search/querybuilder/QueryBuilders.java @@ -10,9 +10,8 @@ * This class contains methods to construct query nodes. These query nodes can be added to parent * query nodes (building a chain) or used as the root query node. */ -public class QueryBuilder { - - private QueryBuilder() { +public class QueryBuilders { + private QueryBuilders() { throw new InstantiationError("Must not instantiate this class"); } diff --git a/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java index 88d2cb376d..a63b625f50 100644 --- a/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java @@ -1,7 +1,7 @@ package redis.clients.jedis.modules.search; import static org.junit.Assert.assertEquals; -import static redis.clients.jedis.search.querybuilder.QueryBuilder.*; +import static redis.clients.jedis.search.querybuilder.QueryBuilders.*; import static redis.clients.jedis.search.querybuilder.Values.*; import java.util.Arrays;