Update handling of criteria in format "table.field" when the "table" portion equals the record's tableName; fix applyBooleanOperator to always update the accumulator;

This commit is contained in:
2023-09-08 10:30:39 -05:00
parent 73e826f81d
commit 6c7621a2f7
2 changed files with 279 additions and 4 deletions

View File

@ -75,14 +75,16 @@ public class BackendQueryFilterUtils
{ {
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
// if the value isn't in the record - check, if it looks like a table.fieldName, but none of the // // if the value isn't in the record - check, if it looks like a table.fieldName, but none of the //
// field names in the record are fully qualified, then just use the field-name portion... // // field names in the record are fully qualified - OR - the table name portion of the field name //
// matches the record's field name, then just use the field-name portion... //
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
if(fieldName.contains(".")) if(fieldName.contains("."))
{ {
String[] parts = fieldName.split("\\.");
Map<String, Serializable> values = qRecord.getValues(); Map<String, Serializable> values = qRecord.getValues();
if(values.keySet().stream().noneMatch(n -> n.contains("."))) if(values.keySet().stream().noneMatch(n -> n.contains(".")) || parts[0].equals(qRecord.getTableName()))
{ {
value = qRecord.getValue(fieldName.substring(fieldName.indexOf(".") + 1)); value = qRecord.getValue(parts[1]);
} }
} }
} }
@ -190,12 +192,13 @@ public class BackendQueryFilterUtils
** operator, update the accumulator, and if we can then short-circuit remaining ** operator, update the accumulator, and if we can then short-circuit remaining
** operations, return a true or false. Returning null means to keep going. ** operations, return a true or false. Returning null means to keep going.
*******************************************************************************/ *******************************************************************************/
private static Boolean applyBooleanOperator(AtomicBoolean accumulator, boolean newValue, QQueryFilter.BooleanOperator booleanOperator) static Boolean applyBooleanOperator(AtomicBoolean accumulator, boolean newValue, QQueryFilter.BooleanOperator booleanOperator)
{ {
boolean accumulatorValue = accumulator.getPlain(); boolean accumulatorValue = accumulator.getPlain();
if(booleanOperator.equals(QQueryFilter.BooleanOperator.AND)) if(booleanOperator.equals(QQueryFilter.BooleanOperator.AND))
{ {
accumulatorValue &= newValue; accumulatorValue &= newValue;
accumulator.set(accumulatorValue);
if(!accumulatorValue) if(!accumulatorValue)
{ {
return (false); return (false);
@ -204,6 +207,7 @@ public class BackendQueryFilterUtils
else else
{ {
accumulatorValue |= newValue; accumulatorValue |= newValue;
accumulator.set(accumulatorValue);
if(accumulatorValue) if(accumulatorValue)
{ {
return (true); return (true);

View File

@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.utils;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
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.utils.collections.ListBuilder; import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -37,6 +42,182 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class BackendQueryFilterUtilsTest class BackendQueryFilterUtilsTest
{ {
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDoesRecordMatch_emptyFilters()
{
assertTrue(BackendQueryFilterUtils.doesRecordMatch(null, new QRecord().withValue("a", 1)));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(new QQueryFilter(), new QRecord().withValue("a", 1)));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(new QQueryFilter().withSubFilters(ListBuilder.of(null)), new QRecord().withValue("a", 1)));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(new QQueryFilter().withSubFilters(List.of(new QQueryFilter())), new QRecord().withValue("a", 1)));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDoesRecordMatch_singleAnd()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 2)));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDoesRecordMatch_singleOr()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 2)));
}
/***************************************************************************
**
***************************************************************************/
@Test
void testDoesRecordMatch_multipleAnd()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1))
.withCriteria(new QFilterCriteria("b", QCriteriaOperator.EQUALS, 2));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 2)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 2).withValue("b", 2)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord()));
}
/***************************************************************************
**
***************************************************************************/
@Test
void testDoesRecordMatch_multipleOr()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1))
.withCriteria(new QFilterCriteria("b", QCriteriaOperator.EQUALS, 2));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 2)));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 2).withValue("b", 2)));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 3).withValue("b", 4)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord()));
}
/***************************************************************************
**
***************************************************************************/
@Test
void testDoesRecordMatch_subFilterAnd()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withSubFilters(List.of(
new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1)),
new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("b", QCriteriaOperator.EQUALS, 2))
));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 2)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 2).withValue("b", 2)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord()));
}
/***************************************************************************
**
***************************************************************************/
@Test
void testDoesRecordMatch_subFilterOr()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withSubFilters(List.of(
new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR)
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1)),
new QQueryFilter()
.withCriteria(new QFilterCriteria("b", QCriteriaOperator.EQUALS, 2))
));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 2)));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 2).withValue("b", 2)));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("b", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 3).withValue("b", 4)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord()));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDoesRecordMatch_criteriaHasTableNameNoFieldsDo()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("t.a", QCriteriaOperator.EQUALS, 1));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1)));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDoesRecordMatch_criteriaHasTableNameSomeFieldsDo()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("t.a", QCriteriaOperator.EQUALS, 1));
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// shouldn't find the "a", because "some" fields in here have a prefix (e.g., 's' was a join table, selected with 't' as the main table, which didn't prefix) //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("a", 1).withValue("s.b", 2)));
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// but this case (contrasted with above) set the record's tableName to "t", so criteria on "t.a" should find field "a" //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withTableName("t").withValue("a", 1).withValue("s.b", 2)));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDoesRecordMatch_criteriaHasTableNameMatchingField()
{
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.AND)
.withCriteria(new QFilterCriteria("t.a", QCriteriaOperator.EQUALS, 1));
assertTrue(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("t.a", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("t.b", 1)));
assertFalse(BackendQueryFilterUtils.doesRecordMatch(filter, new QRecord().withValue("s.a", 1)));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -184,4 +365,94 @@ class BackendQueryFilterUtilsTest
assertFalse("Not Darin".matches(pattern)); assertFalse("Not Darin".matches(pattern));
assertFalse("David".matches(pattern)); assertFalse("David".matches(pattern));
} }
/*******************************************************************************
**
*******************************************************************************/
@Test
void testApplyBooleanOperator()
{
/////////////////////////////
// tests for operator: AND //
/////////////////////////////
{
/////////////////////////////////////////////////////////////////////////////////////
// old value was true; new value is true. //
// result should be true, and we should not be short-circuited (return value null) //
/////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(true);
assertNull(BackendQueryFilterUtils.applyBooleanOperator(accumulator, true, QQueryFilter.BooleanOperator.AND));
assertTrue(accumulator.getPlain());
}
{
//////////////////////////////////////////////////////////////////////////////////////
// old value was true; new value is false. //
// result should be false, and we should be short-circuited (return value not-null) //
//////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(true);
assertEquals(Boolean.FALSE, BackendQueryFilterUtils.applyBooleanOperator(accumulator, false, QQueryFilter.BooleanOperator.AND));
assertFalse(accumulator.getPlain());
}
{
//////////////////////////////////////////////////////////////////////////////////////
// old value was false; new value is true. //
// result should be false, and we should be short-circuited (return value not-null) //
//////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(false);
assertEquals(Boolean.FALSE, BackendQueryFilterUtils.applyBooleanOperator(accumulator, true, QQueryFilter.BooleanOperator.AND));
assertFalse(accumulator.getPlain());
}
{
//////////////////////////////////////////////////////////////////////////////////////
// old value was false; new value is false. //
// result should be false, and we should be short-circuited (return value not-null) //
//////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(false);
assertEquals(Boolean.FALSE, BackendQueryFilterUtils.applyBooleanOperator(accumulator, false, QQueryFilter.BooleanOperator.AND));
assertFalse(accumulator.getPlain());
}
////////////////////////////
// tests for operator: OR //
////////////////////////////
{
/////////////////////////////////////////////////////////////////////////////////////
// old value was true; new value is true. //
// result should be true, and we should be short-circuited (return value not-null) //
/////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(true);
assertEquals(Boolean.TRUE, BackendQueryFilterUtils.applyBooleanOperator(accumulator, true, QQueryFilter.BooleanOperator.OR));
assertTrue(accumulator.getPlain());
}
{
//////////////////////////////////////////////////////////////////////////////////////
// old value was true; new value is false. //
// result should be true, and we should be short-circuited (return value not-null) //
//////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(true);
assertEquals(Boolean.TRUE, BackendQueryFilterUtils.applyBooleanOperator(accumulator, false, QQueryFilter.BooleanOperator.OR));
assertTrue(accumulator.getPlain());
}
{
//////////////////////////////////////////////////////////////////////////////////////
// old value was false; new value is true. //
// result should be false, and we should be short-circuited (return value not-null) //
//////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(false);
assertEquals(Boolean.TRUE, BackendQueryFilterUtils.applyBooleanOperator(accumulator, true, QQueryFilter.BooleanOperator.OR));
assertTrue(accumulator.getPlain());
}
{
//////////////////////////////////////////////////////////////////////////////////////
// old value was false; new value is false. //
// result should be false, and we should not be short-circuited (return value null) //
//////////////////////////////////////////////////////////////////////////////////////
AtomicBoolean accumulator = new AtomicBoolean(false);
assertNull(BackendQueryFilterUtils.applyBooleanOperator(accumulator, false, QQueryFilter.BooleanOperator.OR));
assertFalse(accumulator.getPlain());
}
}
} }