mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Adding booleanOperator and subFilters to QQueryFilter
This commit is contained in:
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 "
|
||||||
|
@ -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()))
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user