diff --git a/build.gradle b/build.gradle
index f332823..142a7b9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -108,6 +108,7 @@ subprojects {
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation group: 'com.redis', name: 'testcontainers-redis', version: testcontainersRedisVersion
testImplementation group: 'com.redis', name: 'testcontainers-redis-enterprise', version: testcontainersRedisVersion
+ testImplementation 'org.mockito:mockito-core:5.11.0'
}
test {
diff --git a/core/lettucemod-query/src/main/java/com/redis/search/query/filter/CompositeCondition.java b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/CompositeCondition.java
index 650d76a..a2b2b15 100644
--- a/core/lettucemod-query/src/main/java/com/redis/search/query/filter/CompositeCondition.java
+++ b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/CompositeCondition.java
@@ -2,9 +2,9 @@
public class CompositeCondition implements Condition {
- private final Condition left;
- private final Condition right;
- private final CharSequence delimiter;
+ protected final Condition left;
+ protected final Condition right;
+ protected final CharSequence delimiter;
public CompositeCondition(CharSequence delimiter, Condition left, Condition right) {
this.delimiter = delimiter;
diff --git a/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Condition.java b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Condition.java
index 88f151e..be7ce0d 100644
--- a/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Condition.java
+++ b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Condition.java
@@ -1,23 +1,29 @@
package com.redis.search.query.filter;
+import java.util.List;
+
public interface Condition {
String getQuery();
default Condition and(Condition condition) {
- return new And(this, condition);
+ return new And(this, condition);
}
default Condition or(Condition condition) {
- return new Or(this, condition);
+ return new Or(this, condition);
+ }
+
+ static Condition orList(final Condition... conditions) {
+ return new OrList(conditions);
}
default Condition not() {
- return new Not(this);
+ return new Not(this);
}
default Condition optional() {
- return new Optional(this);
+ return new Optional(this);
}
}
diff --git a/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Or.java b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Or.java
index b2c61b3..8273572 100644
--- a/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Or.java
+++ b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/Or.java
@@ -5,7 +5,7 @@ public class Or extends CompositeCondition {
public static final String DELIMITER = "|";
public Or(Condition left, Condition right) {
- super(DELIMITER, left, right);
+ super(DELIMITER, left, right);
}
-}
+}
\ No newline at end of file
diff --git a/core/lettucemod-query/src/main/java/com/redis/search/query/filter/OrList.java b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/OrList.java
new file mode 100644
index 0000000..432686c
--- /dev/null
+++ b/core/lettucemod-query/src/main/java/com/redis/search/query/filter/OrList.java
@@ -0,0 +1,75 @@
+package com.redis.search.query.filter;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Represents a logical OR condition composed of multiple {@link Condition} elements.
+ *
+ * This class generates a query string where each condition is wrapped in parentheses
+ * and separated by the OR operator ('|'), suitable for use in Redis Search queries.
+ *
+ * Example output:
+ *
{@code
+ * ( (query1)|(query2)|(query3) )
+ * }
+ */
+public class OrList implements Condition {
+
+ /**
+ * The delimiter used to join conditions in an OR clause.
+ */
+ public static final String OR_LIST_CONDITION_FORMAT = "|";
+
+ private final List conditions;
+
+ /**
+ * Constructs an {@code OrList} with the given conditions.
+ *
+ * @param conditions One or more {@link Condition} objects to be ORed together.
+ */
+ public OrList(Condition... conditions) {
+ this.conditions = Arrays.asList(conditions);
+ }
+
+ /**
+ * Builds and returns the query string representing a logical OR condition.
+ *
+ * Logic:
+ *
+ * - Each valid (non-null and non-empty) condition query is wrapped in parentheses.
+ * - All valid conditions are joined using the OR operator {@code |}.
+ * - If fewer than two valid conditions are present, the query is returned without wrapping the entire string in extra parentheses.
+ * - If no valid conditions are present, an empty string is returned.
+ *
+ *
+ * @return A formatted query string representing the logical OR of valid conditions,
+ * or an empty string if none exist.
+ */
+ @Override
+ public String getQuery() {
+ if (conditions == null || conditions.isEmpty()) {
+ return "";
+ }
+
+ StringJoiner joiner = new StringJoiner(OR_LIST_CONDITION_FORMAT);
+ int validConditionCounter = 0;
+ for (Condition condition : conditions) {
+ if (condition == null) {
+ continue;
+ }
+
+ String query = condition.getQuery();
+ if (query != null && !query.isEmpty()) {
+ joiner.add("(" + query + ")");
+ validConditionCounter++;
+ }
+ }
+ if (joiner.length() == 0 || validConditionCounter < 2) {
+ return joiner.toString();
+ }
+
+ return "(" + joiner.toString() + ")";
+ }
+}
diff --git a/core/lettucemod-query/src/test/java/com/redis/query/TestOrList.java b/core/lettucemod-query/src/test/java/com/redis/query/TestOrList.java
new file mode 100644
index 0000000..f8c6b30
--- /dev/null
+++ b/core/lettucemod-query/src/test/java/com/redis/query/TestOrList.java
@@ -0,0 +1,77 @@
+package com.redis.query;
+
+import com.redis.search.query.filter.Condition;
+import com.redis.search.query.filter.OrList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestOrList {
+ Condition cond1;
+ Condition cond2;
+ Condition cond3;
+
+ @BeforeEach
+ public void setup() {
+ cond1 = Mockito.mock(Condition.class);
+ cond2 = Mockito.mock(Condition.class);
+ cond3 = Mockito.mock(Condition.class);
+ }
+
+ @Test
+ public void testOrCondition_whenConditionsArePresent_thenReturnQuery() {
+ Mockito.when(cond1.getQuery()).thenReturn("@category:{italian}");
+ Mockito.when(cond2.getQuery()).thenReturn("@price:[1000 2000]");
+ Mockito.when(cond3.getQuery()).thenReturn("@rating:[4.0 5.0]");
+
+ OrList orListCondition = new OrList(cond1, cond2, cond3);
+ String orListQuery = orListCondition.getQuery();
+
+ Assertions.assertEquals("((@category:{italian})|(@price:[1000 2000])|(@rating:[4.0 5.0]))", orListQuery);
+ }
+
+ @Test
+ public void testOrCondition_whenConditionsAreEmpty_thenReturnEmptyQuery() {
+ Mockito.when(cond1.getQuery()).thenReturn("");
+ Mockito.when(cond2.getQuery()).thenReturn("");
+ Mockito.when(cond3.getQuery()).thenReturn("");
+
+ OrList orListCondition = new OrList(cond1, cond2, cond3);
+ String orListQuery = orListCondition.getQuery();
+
+ Assertions.assertEquals("", orListQuery);
+ }
+
+ @Test
+ public void testOrCondition_whenConditionsAreNull_thenReturnEmptyQuery() {
+ Mockito.when(cond1.getQuery()).thenReturn(null);
+ Mockito.when(cond2.getQuery()).thenReturn(null);
+ Mockito.when(cond3.getQuery()).thenReturn(null);
+
+ OrList orListCondition = new OrList(cond1, cond2, cond3);
+ String orListQuery = orListCondition.getQuery();
+
+ Assertions.assertEquals("", orListQuery);
+ }
+
+ @Test
+ public void testOrCondition_whenSingleConditionIsPresent_thenReturnQuery() {
+ Mockito.when(cond1.getQuery()).thenReturn("@category:{italian}");
+
+ OrList orListCondition = new OrList(cond1);
+ String orListQuery = orListCondition.getQuery();
+
+ Assertions.assertEquals("(@category:{italian})", orListQuery);
+ }
+
+ @Test
+ public void testOrCondition_whenSingleConditionIsPresentAlongWithNullCondition_thenReturnQuery() {
+ Mockito.when(cond1.getQuery()).thenReturn("@category:{italian}");
+
+ OrList orListCondition = new OrList(cond1, null);
+ String orListQuery = orListCondition.getQuery();
+
+ Assertions.assertEquals("(@category:{italian})", orListQuery);
+ }
+}