Adding booleanOperator and subFilters to QQueryFilter

This commit is contained in:
2022-10-10 08:40:51 -05:00
parent 28060e95e3
commit 262038bc87
8 changed files with 453 additions and 154 deletions

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/******************************************************************************* /*******************************************************************************
@ -36,6 +37,20 @@ public class QQueryFilter implements Serializable, Cloneable
private List<QFilterCriteria> criteria = new ArrayList<>(); private List<QFilterCriteria> criteria = new ArrayList<>();
private List<QFilterOrderBy> orderBys = new ArrayList<>(); private List<QFilterOrderBy> orderBys = new ArrayList<>();
private BooleanOperator booleanOperator = BooleanOperator.AND;
private List<QQueryFilter> subFilters = new ArrayList<>();
/*******************************************************************************
**
*******************************************************************************/
public enum BooleanOperator
{
AND,
OR
}
/******************************************************************************* /*******************************************************************************
@ -66,6 +81,15 @@ public class QQueryFilter implements Serializable, Cloneable
} }
} }
if(subFilters != null)
{
clone.subFilters = new ArrayList<>();
for(QQueryFilter subFilter : subFilters)
{
clone.subFilters.add(subFilter.clone());
}
}
return clone; return clone;
} }
catch(CloneNotSupportedException e) catch(CloneNotSupportedException e)
@ -76,6 +100,32 @@ public class QQueryFilter implements Serializable, Cloneable
/*******************************************************************************
**
*******************************************************************************/
public boolean hasAnyCriteria()
{
if(CollectionUtils.nullSafeHasContents(criteria))
{
return (true);
}
if(CollectionUtils.nullSafeHasContents(subFilters))
{
for(QQueryFilter subFilter : subFilters)
{
if(subFilter.hasAnyCriteria())
{
return (true);
}
}
}
return (false);
}
/******************************************************************************* /*******************************************************************************
** Getter for criteria ** Getter for criteria
** **
@ -168,4 +218,72 @@ public class QQueryFilter implements Serializable, Cloneable
return (this); return (this);
} }
/*******************************************************************************
** Getter for booleanOperator
**
*******************************************************************************/
public BooleanOperator getBooleanOperator()
{
return booleanOperator;
}
/*******************************************************************************
** Setter for booleanOperator
**
*******************************************************************************/
public void setBooleanOperator(BooleanOperator booleanOperator)
{
this.booleanOperator = booleanOperator;
}
/*******************************************************************************
** Fluent setter for booleanOperator
**
*******************************************************************************/
public QQueryFilter withBooleanOperator(BooleanOperator booleanOperator)
{
this.booleanOperator = booleanOperator;
return (this);
}
/*******************************************************************************
** Getter for subFilters
**
*******************************************************************************/
public List<QQueryFilter> getSubFilters()
{
return subFilters;
}
/*******************************************************************************
** Setter for subFilters
**
*******************************************************************************/
public void setSubFilters(List<QQueryFilter> subFilters)
{
this.subFilters = subFilters;
}
/*******************************************************************************
** Fluent setter for subFilters
**
*******************************************************************************/
public QQueryFilter withSubFilters(List<QQueryFilter> subFilters)
{
this.subFilters = subFilters;
return (this);
}
} }

View File

@ -29,6 +29,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
@ -141,126 +142,121 @@ public class MemoryRecordStore
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@SuppressWarnings("checkstyle:indentation")
private boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord) private boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord)
{ {
boolean recordMatches = true; if(filter == null || !filter.hasAnyCriteria())
if(filter != null && filter.getCriteria() != null)
{ {
for(QFilterCriteria criterion : filter.getCriteria()) return (true);
}
/////////////////////////////////////////////////////////////////////////////////////
// for an AND query, default to a TRUE answer, and we'll &= each criteria's value. //
// for an OR query, default to FALSE, and |= each criteria's value. //
/////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean recordMatches = new AtomicBoolean(filter.getBooleanOperator().equals(QQueryFilter.BooleanOperator.AND) ? true : false);
///////////////////////////////////////
// if there are criteria, apply them //
///////////////////////////////////////
for(QFilterCriteria criterion : CollectionUtils.nonNullList(filter.getCriteria()))
{ {
String fieldName = criterion.getFieldName(); String fieldName = criterion.getFieldName();
Serializable value = qRecord.getValue(fieldName); Serializable value = qRecord.getValue(fieldName);
switch(criterion.getOperator()) boolean criterionMatches = switch(criterion.getOperator())
{ {
case EQUALS: case EQUALS -> testEquals(criterion, value);
{ case NOT_EQUALS -> !testEquals(criterion, value);
recordMatches = testEquals(criterion, value); case IN -> testIn(criterion, value);
break; case NOT_IN -> !testIn(criterion, value);
} case IS_BLANK -> testBlank(criterion, value);
case NOT_EQUALS: case IS_NOT_BLANK -> !testBlank(criterion, value);
{ case CONTAINS -> testContains(criterion, fieldName, value);
recordMatches = !testEquals(criterion, value); case NOT_CONTAINS -> !testContains(criterion, fieldName, value);
break; case STARTS_WITH -> testStartsWith(criterion, fieldName, value);
} case NOT_STARTS_WITH -> !testStartsWith(criterion, fieldName, value);
case IN: case ENDS_WITH -> testEndsWith(criterion, fieldName, value);
{ case NOT_ENDS_WITH -> !testEndsWith(criterion, fieldName, value);
recordMatches = testIn(criterion, value); case GREATER_THAN -> testGreaterThan(criterion, value);
break; case GREATER_THAN_OR_EQUALS -> testGreaterThan(criterion, value) || testEquals(criterion, value);
} case LESS_THAN -> !testGreaterThan(criterion, value) && !testEquals(criterion, value);
case NOT_IN: case LESS_THAN_OR_EQUALS -> !testGreaterThan(criterion, value);
{ case BETWEEN ->
recordMatches = !testIn(criterion, value);
break;
}
case IS_BLANK:
{
recordMatches = testBlank(criterion, value);
break;
}
case IS_NOT_BLANK:
{
recordMatches = !testBlank(criterion, value);
break;
}
case CONTAINS:
{
recordMatches = testContains(criterion, fieldName, value);
break;
}
case NOT_CONTAINS:
{
recordMatches = !testContains(criterion, fieldName, value);
break;
}
case STARTS_WITH:
{
recordMatches = testStartsWith(criterion, fieldName, value);
break;
}
case NOT_STARTS_WITH:
{
recordMatches = !testStartsWith(criterion, fieldName, value);
break;
}
case ENDS_WITH:
{
recordMatches = testEndsWith(criterion, fieldName, value);
break;
}
case NOT_ENDS_WITH:
{
recordMatches = !testEndsWith(criterion, fieldName, value);
break;
}
case GREATER_THAN:
{
recordMatches = testGreaterThan(criterion, value);
break;
}
case GREATER_THAN_OR_EQUALS:
{
recordMatches = testGreaterThan(criterion, value) || testEquals(criterion, value);
break;
}
case LESS_THAN:
{
recordMatches = !testGreaterThan(criterion, value) && !testEquals(criterion, value);
break;
}
case LESS_THAN_OR_EQUALS:
{
recordMatches = !testGreaterThan(criterion, value);
break;
}
case BETWEEN:
{ {
QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues()); QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues());
QFilterCriteria criteria1 = new QFilterCriteria().withValues(new ArrayList<>(criterion.getValues())); QFilterCriteria criteria1 = new QFilterCriteria().withValues(new ArrayList<>(criterion.getValues()));
criteria1.getValues().remove(0); criteria1.getValues().remove(0);
recordMatches = (testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value)); yield (testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value));
break;
} }
case NOT_BETWEEN: case NOT_BETWEEN ->
{ {
QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues()); QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues());
QFilterCriteria criteria1 = new QFilterCriteria().withValues(criterion.getValues()); QFilterCriteria criteria1 = new QFilterCriteria().withValues(criterion.getValues());
criteria1.getValues().remove(0); criteria1.getValues().remove(0);
recordMatches = !(testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value)); yield !(testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value));
break;
} }
default: };
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Boolean shortCircuitValue = applyBooleanOperator(recordMatches, criterionMatches, filter.getBooleanOperator());
if(shortCircuitValue != null)
{ {
throw new NotImplementedException("Operator [" + criterion.getOperator() + "] is not yet implemented in the Memory backend."); return (shortCircuitValue);
} }
} }
if(!recordMatches)
////////////////////////////////////////
// apply sub-filters if there are any //
////////////////////////////////////////
for(QQueryFilter subFilter : CollectionUtils.nonNullList(filter.getSubFilters()))
{ {
break; boolean subFilterMatches = doesRecordMatch(subFilter, qRecord);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Boolean shortCircuitValue = applyBooleanOperator(recordMatches, subFilterMatches, filter.getBooleanOperator());
if(shortCircuitValue != null)
{
return (shortCircuitValue);
} }
} }
return (recordMatches.getPlain());
} }
return recordMatches;
/*******************************************************************************
** Based on an incoming boolean value (accumulator), a new value, and a boolean
** operator, update the accumulator, and if we can then short-circuit remaining
** operations, return a true or false. Returning null means to keep going.
*******************************************************************************/
private Boolean applyBooleanOperator(AtomicBoolean accumulator, boolean newValue, QQueryFilter.BooleanOperator booleanOperator)
{
boolean accumulatorValue = accumulator.getPlain();
if(booleanOperator.equals(QQueryFilter.BooleanOperator.AND))
{
accumulatorValue &= newValue;
if(!accumulatorValue)
{
return (false);
}
}
else
{
accumulatorValue |= newValue;
if(accumulatorValue)
{
return (true);
}
}
accumulator.set(accumulatorValue);
return (null);
} }

View File

@ -55,6 +55,7 @@ import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -211,48 +212,49 @@ class MemoryBackendModuleTest
insertInput.setTableName(table.getName()); insertInput.setTableName(table.getName());
insertInput.setRecords(List.of( insertInput.setRecords(List.of(
new QRecord().withValue("id", 1).withValue("name", "Square").withValue("date", LocalDate.of(1980, Month.MAY, 31)), new QRecord().withValue("id", 1).withValue("name", "Square").withValue("date", LocalDate.of(1980, Month.MAY, 31)),
new QRecord().withValue("id", 2).withValue("name", "Triangle").withValue("date", LocalDate.of(1999, Month.DECEMBER, 31)) new QRecord().withValue("id", 2).withValue("name", "Triangle").withValue("date", LocalDate.of(1999, Month.DECEMBER, 31)),
new QRecord().withValue("id", 3).withValue("name", "Circle").withValue("date", LocalDate.of(2022, Month.OCTOBER, 10))
)); ));
new InsertAction().execute(insertInput); new InsertAction().execute(insertInput);
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.IN, List.of(1, 2))).size()); assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.IN, List.of(1, 2))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.IN, List.of(2, 3))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.IN, List.of(3, 4))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.NOT_IN, List.of(3, 4))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.NOT_IN, List.of(4, 5))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.NOT_IN, List.of(2, 3))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.NOT_IN, List.of(2, 3))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Square"))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Square"))).size());
assertEquals("Square", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Square"))).get(0).getValue("name")); assertEquals("Square", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Square"))).get(0).getValue("name"));
assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("notAFieldSoNull", QCriteriaOperator.EQUALS, List.of("Square"))).size()); assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("notAFieldSoNull", QCriteriaOperator.EQUALS, List.of("Square"))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_EQUALS, List.of("notFound"))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_EQUALS, List.of("notFound"))).size());
assertEquals("Square", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_EQUALS, List.of("Triangle"))).get(0).getValue("name")); assertEquals("Square", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_EQUALS, List.of("Triangle", "Circle"))).get(0).getValue("name"));
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of("ria"))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of("ria"))).size());
assertEquals("Triangle", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of("ria"))).get(0).getValue("name")); assertEquals("Triangle", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of("ria"))).get(0).getValue("name"));
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_CONTAINS, List.of("notFound"))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_CONTAINS, List.of("notFound"))).size());
assertEquals("Square", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_CONTAINS, List.of("ria"))).get(0).getValue("name")); assertEquals("Square", queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.NOT_CONTAINS, List.of("le"))).get(0).getValue("name"));
assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.CONTAINS, List.of("ria")))); assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.CONTAINS, List.of("ria"))));
assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of(1)))); assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of(1))));
assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of()))); assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.CONTAINS, List.of())));
assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN, List.of(LocalDate.of(2022, Month.SEPTEMBER, 1)))).size()); assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN, List.of(LocalDate.of(2035, Month.JANUARY, 1)))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size()); assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN, List.of(LocalDate.of(1970, Month.JANUARY, 1)))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN, List.of(LocalDate.of(1970, Month.JANUARY, 1)))).size());
assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, List.of(2))).size()); assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, List.of(3))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, List.of(1))).size()); assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, List.of(1))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, List.of(0))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, List.of(0))).size());
assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(2022, Month.SEPTEMBER, 1)))).size()); assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(2035, Month.JANUARY, 1)))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size()); assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(1970, Month.JANUARY, 1)))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(1970, Month.JANUARY, 1)))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(1980, Month.MAY, 31)))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(LocalDate.of(1980, Month.MAY, 31)))).size());
assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(3))).size()); assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(4))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(2))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(3))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(1))).size()); assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(2))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(0))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN_OR_EQUALS, List.of(1))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN, List.of(LocalDate.of(2022, Month.SEPTEMBER, 1)))).size()); assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN, List.of(LocalDate.of(2022, Month.SEPTEMBER, 1)))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size());
@ -265,7 +267,7 @@ class MemoryBackendModuleTest
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(LocalDate.of(1990, Month.JANUARY, 1)))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(LocalDate.of(1980, Month.MAY, 31)))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(LocalDate.of(1980, Month.MAY, 31)))).size());
assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(LocalDate.of(1970, Month.JANUARY, 1)))).size()); assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("date", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(LocalDate.of(1970, Month.JANUARY, 1)))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(3))).size()); assertEquals(3, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(3))).size());
assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(2))).size()); assertEquals(2, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(2))).size());
assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(1))).size()); assertEquals(1, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(1))).size());
assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(0))).size()); assertEquals(0, queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of(0))).size());
@ -275,16 +277,88 @@ class MemoryBackendModuleTest
assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN, List.of()))); assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN, List.of())));
assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of()))); assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of())));
assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of("Bob")))); assertThrows(QException.class, () -> queryShapes(qInstance, table, session, new QFilterCriteria("name", QCriteriaOperator.LESS_THAN_OR_EQUALS, List.of("Bob"))));
{
////////////////////////////
// test a simple OR query //
////////////////////////////
QQueryFilter filter = new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Square")))
.withCriteria(new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Triangle")));
assertEquals(2, queryShapes(qInstance, table, session, filter).size());
assertThat(queryShapes(qInstance, table, session, filter)).anyMatch(r -> r.getValueString("name").equals("Square"));
assertThat(queryShapes(qInstance, table, session, filter)).anyMatch(r -> r.getValueString("name").equals("Triangle"));
}
///////////////////////////////////////////////////
// null or empty query - should find all records //
///////////////////////////////////////////////////
assertEquals(3, queryShapes(qInstance, table, session, (QQueryFilter) null).size());
assertEquals(3, queryShapes(qInstance, table, session, new QQueryFilter()).size());
{
/////////////////////////////////
// test a complex nested query //
/////////////////////////////////
QQueryFilter filter = new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withSubFilters(List.of(
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Square")))
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.EQUALS, List.of(1))),
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Circle")))
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.EQUALS, List.of(3)))
));
assertEquals(2, queryShapes(qInstance, table, session, filter).size());
assertThat(queryShapes(qInstance, table, session, filter)).anyMatch(r -> r.getValueString("name").equals("Square") && r.getValueInteger("id").equals(1));
assertThat(queryShapes(qInstance, table, session, filter)).anyMatch(r -> r.getValueString("name").equals("Circle") && r.getValueInteger("id").equals(3));
}
{
/////////////////////////////////
// test a complex nested query //
/////////////////////////////////
QQueryFilter filter = new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withSubFilters(List.of(
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.EQUALS, List.of(1)))
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.EQUALS, List.of(3))),
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Square")))
.withCriteria(new QFilterCriteria("name", QCriteriaOperator.EQUALS, List.of("Circle")))
));
assertEquals(2, queryShapes(qInstance, table, session, filter).size());
assertThat(queryShapes(qInstance, table, session, filter)).anyMatch(r -> r.getValueString("name").equals("Square") && r.getValueInteger("id").equals(1));
assertThat(queryShapes(qInstance, table, session, filter)).anyMatch(r -> r.getValueString("name").equals("Circle") && r.getValueInteger("id").equals(3));
}
} }
/*******************************************************************************
**
*******************************************************************************/
private List<QRecord> queryShapes(QInstance qInstance, QTableMetaData table, QSession session, QFilterCriteria criteria) throws QException private List<QRecord> queryShapes(QInstance qInstance, QTableMetaData table, QSession session, QFilterCriteria criteria) throws QException
{
return queryShapes(qInstance, table, session, new QQueryFilter().withCriteria(criteria));
}
private List<QRecord> queryShapes(QInstance qInstance, QTableMetaData table, QSession session, QQueryFilter filter) throws QException
{ {
QueryInput queryInput = new QueryInput(qInstance); QueryInput queryInput = new QueryInput(qInstance);
queryInput.setSession(session); queryInput.setSession(session);
queryInput.setTableName(table.getName()); queryInput.setTableName(table.getName());
queryInput.setFilter(new QQueryFilter().withCriteria(criteria)); queryInput.setFilter(filter);
QueryOutput queryOutput = new QueryAction().execute(queryInput); QueryOutput queryOutput = new QueryAction().execute(queryInput);
return queryOutput.getRecords(); return queryOutput.getRecords();
} }

View File

@ -34,10 +34,12 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.QActionInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
@ -161,7 +163,42 @@ public abstract class AbstractRDBMSAction implements QActionInterface
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
protected String makeWhereClause(QTableMetaData table, List<QFilterCriteria> criteria, List<Serializable> params) throws IllegalArgumentException protected String makeWhereClause(QTableMetaData table, QQueryFilter filter, List<Serializable> params) throws IllegalArgumentException
{
String clause = makeWhereClause(table, filter.getCriteria(), filter.getBooleanOperator(), params);
if(!CollectionUtils.nullSafeHasContents(filter.getSubFilters()))
{
///////////////////////////////////////////////////////////////
// if there are no sub-clauses, then just return this clause //
///////////////////////////////////////////////////////////////
return (clause);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// else, build a list of clauses - recursively expanding the sub-filters into clauses, then return them joined with our operator //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
List<String> clauses = new ArrayList<>();
if(StringUtils.hasContent(clause))
{
clauses.add("(" + clause + ")");
}
for(QQueryFilter subFilter : filter.getSubFilters())
{
String subClause = makeWhereClause(table, subFilter, params);
if(StringUtils.hasContent(subClause))
{
clauses.add("(" + subClause + ")");
}
}
return (String.join(" " + filter.getBooleanOperator().toString() + " ", clauses));
}
/*******************************************************************************
**
*******************************************************************************/
private String makeWhereClause(QTableMetaData table, List<QFilterCriteria> criteria, QQueryFilter.BooleanOperator booleanOperator, List<Serializable> params) throws IllegalArgumentException
{ {
List<String> clauses = new ArrayList<>(); List<String> clauses = new ArrayList<>();
for(QFilterCriteria criterion : criteria) for(QFilterCriteria criterion : criteria)
@ -175,13 +212,13 @@ public abstract class AbstractRDBMSAction implements QActionInterface
{ {
case EQUALS: case EQUALS:
{ {
clause += " = ? "; clause += " = ?";
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case NOT_EQUALS: case NOT_EQUALS:
{ {
clause += " != ? "; clause += " != ?";
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
@ -196,7 +233,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
} }
else else
{ {
clause += " IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") "; clause += " IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ")";
} }
break; break;
} }
@ -211,105 +248,105 @@ public abstract class AbstractRDBMSAction implements QActionInterface
} }
else else
{ {
clause += " NOT IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") "; clause += " NOT IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ")";
} }
break; break;
} }
case STARTS_WITH: case STARTS_WITH:
{ {
clause += " LIKE ? "; clause += " LIKE ?";
editFirstValue(values, (s -> s + "%")); editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case ENDS_WITH: case ENDS_WITH:
{ {
clause += " LIKE ? "; clause += " LIKE ?";
editFirstValue(values, (s -> "%" + s)); editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case CONTAINS: case CONTAINS:
{ {
clause += " LIKE ? "; clause += " LIKE ?";
editFirstValue(values, (s -> "%" + s + "%")); editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case NOT_STARTS_WITH: case NOT_STARTS_WITH:
{ {
clause += " NOT LIKE ? "; clause += " NOT LIKE ?";
editFirstValue(values, (s -> s + "%")); editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case NOT_ENDS_WITH: case NOT_ENDS_WITH:
{ {
clause += " NOT LIKE ? "; clause += " NOT LIKE ?";
editFirstValue(values, (s -> "%" + s)); editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case NOT_CONTAINS: case NOT_CONTAINS:
{ {
clause += " NOT LIKE ? "; clause += " NOT LIKE ?";
editFirstValue(values, (s -> "%" + s + "%")); editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case LESS_THAN: case LESS_THAN:
{ {
clause += " < ? "; clause += " < ?";
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case LESS_THAN_OR_EQUALS: case LESS_THAN_OR_EQUALS:
{ {
clause += " <= ? "; clause += " <= ?";
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case GREATER_THAN: case GREATER_THAN:
{ {
clause += " > ? "; clause += " > ?";
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case GREATER_THAN_OR_EQUALS: case GREATER_THAN_OR_EQUALS:
{ {
clause += " >= ? "; clause += " >= ?";
expectedNoOfParams = 1; expectedNoOfParams = 1;
break; break;
} }
case IS_BLANK: case IS_BLANK:
{ {
clause += " IS NULL "; clause += " IS NULL";
if(isString(field.getType())) if(isString(field.getType()))
{ {
clause += " OR " + column + " = '' "; clause += " OR " + column + " = ''";
} }
expectedNoOfParams = 0; expectedNoOfParams = 0;
break; break;
} }
case IS_NOT_BLANK: case IS_NOT_BLANK:
{ {
clause += " IS NOT NULL "; clause += " IS NOT NULL";
if(isString(field.getType())) if(isString(field.getType()))
{ {
clause += " AND " + column + " != '' "; clause += " AND " + column + " != ''";
} }
expectedNoOfParams = 0; expectedNoOfParams = 0;
break; break;
} }
case BETWEEN: case BETWEEN:
{ {
clause += " BETWEEN ? AND ? "; clause += " BETWEEN ? AND ?";
expectedNoOfParams = 2; expectedNoOfParams = 2;
break; break;
} }
case NOT_BETWEEN: case NOT_BETWEEN:
{ {
clause += " NOT BETWEEN ? AND ? "; clause += " NOT BETWEEN ? AND ?";
expectedNoOfParams = 2; expectedNoOfParams = 2;
break; break;
} }
@ -330,7 +367,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
params.addAll(values); params.addAll(values);
} }
return (String.join(" AND ", clauses)); return (String.join(" " + booleanOperator.toString() + " ", clauses));
} }

View File

@ -33,7 +33,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -62,9 +61,9 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
QQueryFilter filter = countInput.getFilter(); QQueryFilter filter = countInput.getFilter();
List<Serializable> params = new ArrayList<>(); List<Serializable> params = new ArrayList<>();
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getCriteria())) if(filter != null && filter.hasAnyCriteria())
{ {
sql += " WHERE " + makeWhereClause(table, filter.getCriteria(), params); sql += " WHERE " + makeWhereClause(table, filter, params);
} }
// todo sql customization - can edit sql and/or param list // todo sql customization - can edit sql and/or param list

View File

@ -48,6 +48,7 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
private static final Logger LOG = LogManager.getLogger(RDBMSDeleteAction.class); private static final Logger LOG = LogManager.getLogger(RDBMSDeleteAction.class);
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -258,7 +259,7 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
QTableMetaData table = deleteInput.getTable(); QTableMetaData table = deleteInput.getTable();
String tableName = getTableName(table); String tableName = getTableName(table);
String whereClause = makeWhereClause(table, filter.getCriteria(), params); String whereClause = makeWhereClause(table, filter, params);
// todo sql customization - can edit sql and/or param list? // todo sql customization - can edit sql and/or param list?
String sql = "DELETE FROM " String sql = "DELETE FROM "

View File

@ -76,9 +76,9 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
QQueryFilter filter = queryInput.getFilter(); QQueryFilter filter = queryInput.getFilter();
List<Serializable> params = new ArrayList<>(); List<Serializable> params = new ArrayList<>();
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getCriteria())) if(filter != null && filter.hasAnyCriteria())
{ {
sql += " WHERE " + makeWhereClause(table, filter.getCriteria(), params); sql += " WHERE " + makeWhereClause(table, filter, params);
} }
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys())) if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))

View File

@ -519,7 +519,81 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.NOT_IN, List.of()))); queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.NOT_IN, List.of())));
queryOutput = new QueryAction().execute(queryInput); queryOutput = new QueryAction().execute(queryInput);
Assertions.assertEquals(5, queryOutput.getRecords().size(), "NOT_IN empty list should find everything."); Assertions.assertEquals(5, queryOutput.getRecords().size(), "NOT_IN empty list should find everything.");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testOr() throws QException
{
QueryInput queryInput = initQueryRequest();
queryInput.setFilter(new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Darin")))
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Tim")))
);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
Assertions.assertEquals(2, queryOutput.getRecords().size(), "OR should find 2 rows");
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin"));
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNestedFilterAndOrOr() throws QException
{
QueryInput queryInput = initQueryRequest();
queryInput.setFilter(new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withSubFilters(List.of(
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("James")))
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Maes"))),
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Darin")))
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Kelkhoff")))
))
);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Complex query should find 2 rows");
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("lastName").equals("Maes"));
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("lastName").equals("Kelkhoff"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNestedFilterOrAndAnd() throws QException
{
QueryInput queryInput = initQueryRequest();
queryInput.setFilter(new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withSubFilters(List.of(
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("James")))
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Tim"))),
new QQueryFilter()
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Kelkhoff")))
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Chamberlain")))
))
);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Complex query should find 1 row");
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim") && r.getValueString("lastName").equals("Chamberlain"));
} }
} }