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;