mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Merge pull request #66 from Kingsrook/feature/CE-798-quick-filters-fixes
CE-798 - Add de-duplication of (some) redundant criteria in dashboard…
This commit is contained in:
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.QQueryFilterDeduper;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -176,11 +177,13 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
{
|
{
|
||||||
return (totalString);
|
return (totalString);
|
||||||
}
|
}
|
||||||
|
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||||
return ("<a href='" + tablePath + "?filter=" + JsonUtils.toJson(filter) + "'>" + totalString + "</a>");
|
return ("<a href='" + tablePath + "?filter=" + JsonUtils.toJson(filter) + "'>" + totalString + "</a>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -192,6 +195,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||||
urls.add(tablePath + "?filter=" + JsonUtils.toJson(filter));
|
urls.add(tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,6 +212,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||||
return (tablePath + "?filter=" + JsonUtils.toJson(filter));
|
return (tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +229,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||||
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,6 +332,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||||
|
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||||
return (tablePath + "/" + processName + "?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
return (tablePath + "/" + processName + "?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
||||||
@ -346,4 +347,37 @@ public class QFilterCriteria implements Serializable, Cloneable
|
|||||||
return (rs.toString());
|
return (rs.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o == null || getClass() != o.getClass())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFilterCriteria that = (QFilterCriteria) o;
|
||||||
|
return Objects.equals(fieldName, that.fieldName) && operator == that.operator && Objects.equals(values, that.values) && Objects.equals(otherFieldName, that.otherFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Objects.hash(fieldName, operator, values, otherFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
@ -467,4 +468,36 @@ public class QQueryFilter implements Serializable, Cloneable
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o == null || getClass() != o.getClass())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QQueryFilter that = (QQueryFilter) o;
|
||||||
|
return Objects.equals(criteria, that.criteria) && Objects.equals(orderBys, that.orderBys) && booleanOperator == that.booleanOperator && Objects.equals(subFilters, that.subFilters) && Objects.equals(skip, that.skip) && Objects.equals(limit, that.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Objects.hash(criteria, orderBys, booleanOperator, subFilters, skip, limit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,363 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.IN;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_EQUALS;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_IN;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Class to help deduplicate redundant criteria in filters.
|
||||||
|
**
|
||||||
|
** Original use-case is for making more clean url links out of filters.
|
||||||
|
**
|
||||||
|
** Does not (at this time) look into sub-filters at all, or support any "OR"
|
||||||
|
** filters other than the most basic (a=1 OR a=1).
|
||||||
|
**
|
||||||
|
** Also, other than for completely redundant criteria (e.g., a>1 and a>1) only
|
||||||
|
* works on a limited subset of criteria operators (EQUALS, NOT_EQUALS, IN, and NOT_IN)
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QQueryFilterDeduper
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(QQueryFilterDeduper.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QQueryFilter dedupeFilter(QQueryFilter filter)
|
||||||
|
{
|
||||||
|
if(filter == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// track (just for logging) if we failed or if we did any good //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
List<String> log = new ArrayList<>();
|
||||||
|
boolean fail = false;
|
||||||
|
boolean didAnyGood = false;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// always create a clone to be returned. this is especially useful because, //
|
||||||
|
// the clone's lists will be ArrayLists, which are mutable - since some of the deduping //
|
||||||
|
// involves manipulating value lists. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QQueryFilter rs = filter.clone();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// general strategy is: //
|
||||||
|
// iterate over criteria, possibly removing the one the iterator is pointing at, //
|
||||||
|
// if we are able to somehow merge it into other criteria we've already seen. //
|
||||||
|
// the others-we've-seen will be tracked in the criteriaByFieldName listing hash. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
ListingHash<String, QFilterCriteria> criteriaByFieldName = new ListingHash<>();
|
||||||
|
Iterator<QFilterCriteria> iterator = rs.getCriteria().iterator();
|
||||||
|
while(iterator.hasNext())
|
||||||
|
{
|
||||||
|
QFilterCriteria criteria = iterator.next();
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// first thing to check is, have we seen any other criteria for this field - if so - try to do some de-duping. //
|
||||||
|
// note that, any time we do a remove, we'll need to do a continue - to avoid adding the now-removed criteria //
|
||||||
|
// to the listing hash //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(criteriaByFieldName.containsKey(criteria.getFieldName()))
|
||||||
|
{
|
||||||
|
List<QFilterCriteria> others = criteriaByFieldName.get(criteria.getFieldName());
|
||||||
|
QFilterCriteria other = others.get(0);
|
||||||
|
|
||||||
|
if(others.size() == 1 && other.equals(criteria))
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we've only see 1 other criteria for this field so far, and this one is an exact match, then remove this one. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
log.add(String.format("Remove duplicate criteria [%s]", criteria));
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// else - if there's still just 1 other, and it's an AND query - then apply some basic //
|
||||||
|
// logic-merging operations, based on the pair of criteria operators //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(others.size() == 1 && QQueryFilter.BooleanOperator.AND.equals(filter.getBooleanOperator()))
|
||||||
|
{
|
||||||
|
if((NOT_EQUALS.equals(other.getOperator()) || NOT_IN.equals(other.getOperator())) && EQUALS.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we previously saw a not-equals or not-in, and now we see an equals //
|
||||||
|
// and the value from the EQUALS isn't in the not-in list //
|
||||||
|
// then replace the not-equals with the equals //
|
||||||
|
// then just discard this equals //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
if(other.getValues().contains(criteria.getValues().get(0)))
|
||||||
|
{
|
||||||
|
log.add("Contradicting NOT_EQUALS/NOT_IN and EQUALS");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
other.setOperator(criteria.getOperator());
|
||||||
|
other.setValues(criteria.getValues());
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Replace a not-equals or not-in superseded by an equals");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(EQUALS.equals(other.getOperator()) && (NOT_EQUALS.equals(criteria.getOperator()) || NOT_IN.equals(criteria.getOperator())))
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we previously saw an equals, and now we see a not-equals or a not-in //
|
||||||
|
// and the value from the EQUALS isn't in the not-in list //
|
||||||
|
// then just discard this not-equals //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(criteria.getValues().contains(other.getValues().get(0)))
|
||||||
|
{
|
||||||
|
log.add("Contradicting NOT_EQUALS/NOT_IN and EQUALS");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Remove a redundant not-equals");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(NOT_EQUALS.equals(other.getOperator()) && IN.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we previously saw a not-equals, and now we see an IN //
|
||||||
|
// then replace the not-equals with the IN (making sure the not-equals value isn't in the in-list) //
|
||||||
|
// then just discard this equals //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Serializable notEqualsValue = other.getValues().get(0);
|
||||||
|
List<Serializable> inValues = new ArrayList<>(criteria.getValues());
|
||||||
|
inValues.remove(notEqualsValue);
|
||||||
|
if(inValues.isEmpty())
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if the only in-value was the not-equal value, then... i don't know, don't try //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
log.add("Contradicting IN and NOT_EQUAL");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// else, we can proceed by replacing the not-equals with the in //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
other.setOperator(criteria.getOperator());
|
||||||
|
other.setValues(criteria.getValues());
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Replace superseded not-equals (removing its value from in-list)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(IN.equals(other.getOperator()) && NOT_EQUALS.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// if we previously saw an in, and now we see a not-equals //
|
||||||
|
// discard the not-equals (removing its value from the in-list) //
|
||||||
|
// then just discard this not-equals //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
Serializable notEqualsValue = criteria.getValues().get(0);
|
||||||
|
List<Serializable> inValues = new ArrayList<>(other.getValues());
|
||||||
|
inValues.remove(notEqualsValue);
|
||||||
|
if(inValues.isEmpty())
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if the only in-value was the not-equal value, then... i don't know, don't try //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
log.add("Contradicting IN and NOT_EQUAL");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// else, we can proceed by replacing the not-equals with the in //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Remove redundant not-equals (removing its value from in-list)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(NOT_EQUALS.equals(other.getOperator()) && NOT_IN.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we previously saw a not-equals, and now we see a not-in //
|
||||||
|
// we can change the not-equals to the not-in, and make sure it's value is in the list //
|
||||||
|
// then just discard this not-in //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Serializable originalNotEqualsValue = other.getValues().get(0);
|
||||||
|
other.setOperator(criteria.getOperator());
|
||||||
|
other.setValues(criteria.getValues());
|
||||||
|
if(!other.getValues().contains(originalNotEqualsValue))
|
||||||
|
{
|
||||||
|
other.getValues().add(originalNotEqualsValue);
|
||||||
|
}
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Replace superseded not-equals with not-in");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(NOT_IN.equals(other.getOperator()) && NOT_EQUALS.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we previously saw a not-in, and now we see a not-equals //
|
||||||
|
// we can discard this not-equals, and just make sure its value is in the not-in list //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Serializable originalNotEqualsValue = criteria.getValues().get(0);
|
||||||
|
if(!other.getValues().contains(originalNotEqualsValue))
|
||||||
|
{
|
||||||
|
other.getValues().add(originalNotEqualsValue);
|
||||||
|
}
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Remove not-equals, absorbing into not-in");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(NOT_IN.equals(other.getOperator()) && NOT_IN.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
// for multiple not-ins, just merge their values (as a union) //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
for(Serializable value : criteria.getValues())
|
||||||
|
{
|
||||||
|
if(!other.getValues().contains(value))
|
||||||
|
{
|
||||||
|
other.getValues().add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Merging not-ins");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(IN.equals(other.getOperator()) && IN.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// for multiple not-ins, just merge their values (as an intersection) //
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
Set<Serializable> otherValues = new HashSet<>(other.getValues());
|
||||||
|
Set<Serializable> criteriaValues = new HashSet<>(criteria.getValues());
|
||||||
|
otherValues.retainAll(criteriaValues);
|
||||||
|
if(otherValues.isEmpty())
|
||||||
|
{
|
||||||
|
log.add("Contradicting IN lists (no values)");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
other.setValues(new ArrayList<>(otherValues));
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Merging not-ins");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(NOT_EQUALS.equals(other.getOperator()) && NOT_EQUALS.equals(criteria.getOperator()))
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we have 2 not-equals, we can merge them in a not-in //
|
||||||
|
// we can assume their values are different, else they'd have been equals up above //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
other.setOperator(NOT_IN);
|
||||||
|
other.setValues(new ArrayList<>(List.of(other.getValues().get(0), criteria.getValues().get(0))));
|
||||||
|
iterator.remove();
|
||||||
|
didAnyGood = true;
|
||||||
|
log.add("Merge two not-equals as not-in");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.add("Fail because unhandled operator pair");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.add("Fail because > 1 other or operator: OR");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we reach here (e.g., no continue), then assuming we didn't remove the criteria, add it to the listing hash. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
criteriaByFieldName.add(criteria.getFieldName(), criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////
|
||||||
|
// log based on booleans //
|
||||||
|
///////////////////////////
|
||||||
|
if(fail && didAnyGood)
|
||||||
|
{
|
||||||
|
LOG.info("Partially unsuccessful dedupe of filter", logPair("original", filter), logPair("deduped", rs), logPair("log", log));
|
||||||
|
}
|
||||||
|
else if(fail)
|
||||||
|
{
|
||||||
|
LOG.info("Unsuccessful dedupe of filter", logPair("filter", filter), logPair("log", log));
|
||||||
|
}
|
||||||
|
else if(didAnyGood)
|
||||||
|
{
|
||||||
|
LOG.debug("Successful dedupe of filter", logPair("original", filter), logPair("deduped", rs), logPair("log", log));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG.debug("No duplicates in filter, so nothing to dedupe", logPair("original", filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error de-duping filter", e, logPair("filter", filter));
|
||||||
|
return (filter.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.dashboard;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
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.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for AbstractHTMLWidgetRenderer
|
||||||
|
*******************************************************************************/
|
||||||
|
class AbstractHTMLWidgetRendererTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void test() throws QException
|
||||||
|
{
|
||||||
|
String link = AbstractHTMLWidgetRenderer.getCountLink(null, TestUtils.TABLE_NAME_PERSON, new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1)), 2
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
// assert that filter de-duplication is occurring //
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
assertThat(link).doesNotMatch(".*EQUALS.*EQUALS.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.GREATER_THAN;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.IN;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_EQUALS;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_IN;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter.BooleanOperator.OR;
|
||||||
|
import static com.kingsrook.qqq.backend.core.utils.QQueryFilterDeduper.dedupeFilter;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for QQueryFilterDeduper
|
||||||
|
*******************************************************************************/
|
||||||
|
class QQueryFilterDeduperTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testDegenerateCases()
|
||||||
|
{
|
||||||
|
assertNull(dedupeFilter(null));
|
||||||
|
|
||||||
|
QQueryFilter empty = new QQueryFilter();
|
||||||
|
assertEquals(empty, dedupeFilter(empty));
|
||||||
|
assertNotSame(empty, dedupeFilter(empty)); // method always clones, so, just assert that.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testSimpleFiltersWithNoChanges()
|
||||||
|
{
|
||||||
|
QQueryFilter oneCriteria = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1));
|
||||||
|
assertEquals(oneCriteria, dedupeFilter(oneCriteria));
|
||||||
|
assertNotSame(oneCriteria, dedupeFilter(oneCriteria));
|
||||||
|
|
||||||
|
QQueryFilter twoCriteriaDifferentFields = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("b", GREATER_THAN, 2));
|
||||||
|
assertEquals(twoCriteriaDifferentFields, dedupeFilter(twoCriteriaDifferentFields));
|
||||||
|
assertNotSame(twoCriteriaDifferentFields, dedupeFilter(twoCriteriaDifferentFields));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOrs()
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
// we've only written the simplest cases with ORs... //
|
||||||
|
///////////////////////////////////////////////////////
|
||||||
|
assertEquals(new QQueryFilter().withBooleanOperator(OR).withCriteria(new QFilterCriteria("a", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withBooleanOperator(OR)
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// just not built at this time - obviously, could become an IN list //
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
QQueryFilter notSupportedOrTwoEquals = new QQueryFilter()
|
||||||
|
.withBooleanOperator(OR)
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 2));
|
||||||
|
assertEquals(notSupportedOrTwoEquals, dedupeFilter(notSupportedOrTwoEquals));
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// I think the logic would be, that the EQUALS 1 would be removed (is redundant) //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QQueryFilter notSupportedOrEqualsNotEquals = new QQueryFilter()
|
||||||
|
.withBooleanOperator(OR)
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2));
|
||||||
|
assertEquals(notSupportedOrEqualsNotEquals, dedupeFilter(notSupportedOrEqualsNotEquals));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testMoreOperators()
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// only simplest case (of criteria being .equals()) is supported... //
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("a", GREATER_THAN, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", GREATER_THAN, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("a", GREATER_THAN, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// in theory, we could do more, but we just haven't yet (e.g, this could be > 5) //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QQueryFilter tooComplex = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", GREATER_THAN, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", GREATER_THAN, 5));
|
||||||
|
assertEquals(tooComplex, dedupeFilter(tooComplex));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testAllEquals()
|
||||||
|
{
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("a", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("a", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("c", EQUALS, 3)),
|
||||||
|
dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("c", EQUALS, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("c", EQUALS, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("c", EQUALS, 3))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testEqualsAndNotEqualsAndNotIn()
|
||||||
|
{
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 4))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 4))
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 4))
|
||||||
|
));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// this is a contradiction, so we choose not to dedupe it //
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
QQueryFilter contradiction1 = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1));
|
||||||
|
assertEquals(contradiction1, dedupeFilter(contradiction1));
|
||||||
|
|
||||||
|
QQueryFilter contradiction2 = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 0, 1));
|
||||||
|
assertEquals(contradiction2, dedupeFilter(contradiction2));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this case can collapse the two not-equals, but then fails to merge the equals with them, because they are a contradiction! //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertEquals(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 2)),
|
||||||
|
dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", EQUALS, 2))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testNotEqualsAndNotIn()
|
||||||
|
{
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 1, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||||
|
));
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ideally, maybe, this would have the values ordered 1,2,3, but, is equivalent enough //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3, 1)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 1, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 1, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInAndNotEquals()
|
||||||
|
{
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 2, 3))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 2, 3))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||||
|
));
|
||||||
|
|
||||||
|
QQueryFilter contradiction1 = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 1));
|
||||||
|
assertEquals(contradiction1, dedupeFilter(contradiction1));
|
||||||
|
|
||||||
|
QQueryFilter contradiction2 = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1));
|
||||||
|
assertEquals(contradiction2, dedupeFilter(contradiction2));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testMultipleInLists()
|
||||||
|
{
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 2)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 1, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 2, 3))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 3, 4)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 1, 2, 3, 4))
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 3, 4, 5, 6))
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 3)), dedupeFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 1, 2, 3, 4))
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 3, 4, 5, 6))
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 1, 3, 5, 7))
|
||||||
|
));
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
// contradicting in-lists - we give up and refuse to simplify it //
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
QQueryFilter contradiction = new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 1, 2))
|
||||||
|
.withCriteria(new QFilterCriteria("f", IN, 3, 4));
|
||||||
|
assertEquals(contradiction, dedupeFilter(contradiction));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user