Skip to content

Implemented Or List condition to support expression building. #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* <p>
* Example output:
* <pre>{@code
* ( (query1)|(query2)|(query3) )
* }</pre>
*/
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<Condition> 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.
* <p>
* Logic:
* <ul>
* <li>Each valid (non-null and non-empty) condition query is wrapped in parentheses.</li>
* <li>All valid conditions are joined using the OR operator {@code |}.</li>
* <li>If fewer than two valid conditions are present, the query is returned without wrapping the entire string in extra parentheses.</li>
* <li>If no valid conditions are present, an empty string is returned.</li>
* </ul>
*
* @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() + ")";
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}