diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QQueryFilter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QQueryFilter.java index b78d7a91..dba1a93e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QQueryFilter.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QQueryFilter.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression; @@ -469,7 +470,12 @@ public class QQueryFilter implements Serializable, Cloneable *******************************************************************************/ private String getBackendName(QFilterCriteria criterion, int valueIndex) { - StringBuilder backendName = new StringBuilder(criterion.getFieldName()); + StringBuilder backendName = new StringBuilder(); + for(String fieldNameParts : criterion.getFieldName().split("\\.")) + { + backendName.append(StringUtils.ucFirst(fieldNameParts)); + } + for(String operatorParts : criterion.getOperator().name().split("_")) { backendName.append(StringUtils.ucFirst(operatorParts.toLowerCase())); @@ -487,7 +493,7 @@ public class QQueryFilter implements Serializable, Cloneable } } - return (backendName.toString()); + return (StringUtils.lcFirst(backendName.toString())); } @@ -502,6 +508,8 @@ public class QQueryFilter implements Serializable, Cloneable *******************************************************************************/ public void interpretValues(Map inputValues) throws QException { + List caughtExceptions = new ArrayList<>(); + QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter(); variableInterpreter.addValueMap("input", inputValues); for(QFilterCriteria criterion : getCriteria()) @@ -512,31 +520,45 @@ public class QQueryFilter implements Serializable, Cloneable for(Serializable value : criterion.getValues()) { - if(value instanceof AbstractFilterExpression) + try { - /////////////////////////////////////////////////////////////////////// - // if a filter variable expression, evaluate the input values, which // - // will replace the variables with the corresponding actual values // - /////////////////////////////////////////////////////////////////////// - if(value instanceof FilterVariableExpression filterVariableExpression) + if(value instanceof AbstractFilterExpression) { - newValues.add(filterVariableExpression.evaluateInputValues(inputValues)); + /////////////////////////////////////////////////////////////////////// + // if a filter variable expression, evaluate the input values, which // + // will replace the variables with the corresponding actual values // + /////////////////////////////////////////////////////////////////////// + if(value instanceof FilterVariableExpression filterVariableExpression) + { + newValues.add(filterVariableExpression.evaluateInputValues(inputValues)); + } + else + { + newValues.add(value); + } } else { - newValues.add(value); + String valueAsString = ValueUtils.getValueAsString(value); + Serializable interpretedValue = variableInterpreter.interpretForObject(valueAsString); + newValues.add(interpretedValue); } } - else + catch(Exception e) { - String valueAsString = ValueUtils.getValueAsString(value); - Serializable interpretedValue = variableInterpreter.interpretForObject(valueAsString); - newValues.add(interpretedValue); + caughtExceptions.add(e); } } criterion.setValues(newValues); } } + + if(!caughtExceptions.isEmpty()) + { + String message = "Error interpreting filter values: " + StringUtils.joinWithCommasAndAnd(caughtExceptions.stream().map(e -> e.getMessage()).toList()); + boolean allUserFacing = caughtExceptions.stream().allMatch(QUserFacingException.class::isInstance); + throw (allUserFacing ? new QUserFacingException(message) : new QException(message)); + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/FilterVariableExpression.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/FilterVariableExpression.java index 43ad0e76..5c3be8f7 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/FilterVariableExpression.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/FilterVariableExpression.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.util.Map; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; /******************************************************************************* @@ -57,9 +58,9 @@ public class FilterVariableExpression extends AbstractFilterExpression inputValues) throws QException { - if(!inputValues.containsKey(variableName)) + if(!inputValues.containsKey(variableName) || "".equals(ValueUtils.getValueAsString(inputValues.get(variableName)))) { - throw (new QUserFacingException("Missing variable value.")); + throw (new QUserFacingException("Missing value for variable: " + variableName)); } return (inputValues.get(variableName)); } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QQueryFilterTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QQueryFilterTest.java index 01b997d2..1257d7e2 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QQueryFilterTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QQueryFilterTest.java @@ -23,10 +23,12 @@ package com.kingsrook.qqq.backend.core.actions.tables; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.kingsrook.qqq.backend.core.BaseTest; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; 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.actions.tables.query.expressions.AbstractFilterExpression; @@ -35,6 +37,7 @@ import org.junit.jupiter.api.Test; import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.BETWEEN; 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.IS_BLANK; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -65,6 +68,43 @@ class QQueryFilterTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testInterpretValuesNotInMap() throws QException + { + AbstractFilterExpression expression = new FilterVariableExpression() + .withVariableName("clientIdEquals1"); + + QQueryFilter qQueryFilter = new QQueryFilter(new QFilterCriteria("id", EQUALS, expression)); + assertThatThrownBy(() -> qQueryFilter.interpretValues(Collections.emptyMap())) + .isInstanceOf(QUserFacingException.class) + .hasMessageContaining("Missing value for variable: clientIdEquals1"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testInterpretValuesEmptyString() throws QException + { + Map inputValues = new HashMap<>(); + inputValues.put("clientIdEquals1", ""); + + AbstractFilterExpression expression = new FilterVariableExpression() + .withVariableName("clientIdEquals1"); + + QQueryFilter qQueryFilter = new QQueryFilter(new QFilterCriteria("id", EQUALS, expression)); + assertThatThrownBy(() -> qQueryFilter.interpretValues(inputValues)) + .isInstanceOf(QUserFacingException.class) + .hasMessageContaining("Missing value for variable: clientIdEquals1"); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -78,13 +118,15 @@ class QQueryFilterTest extends BaseTest FilterVariableExpression fve4 = new FilterVariableExpression(); FilterVariableExpression fve5 = new FilterVariableExpression(); FilterVariableExpression fve6 = new FilterVariableExpression(); + FilterVariableExpression fve7 = new FilterVariableExpression(); QQueryFilter qQueryFilter = new QQueryFilter( new QFilterCriteria("id", EQUALS, fve0), new QFilterCriteria("value", IS_BLANK, fve1), new QFilterCriteria("id", EQUALS, fve2), new QFilterCriteria("id", BETWEEN, fve3, fve4), - new QFilterCriteria("id", BETWEEN, fve5, fve6) + new QFilterCriteria("id", BETWEEN, fve5, fve6), + new QFilterCriteria("joinTable.someFieldId", EQUALS, fve7) ); qQueryFilter.prepForBackend(); @@ -95,6 +137,7 @@ class QQueryFilterTest extends BaseTest assertEquals("idBetweenTo", fve4.getVariableName()); assertEquals("idBetweenFrom2", fve5.getVariableName()); assertEquals("idBetweenTo2", fve6.getVariableName()); + assertEquals("joinTableSomeFieldIdEquals", fve7.getVariableName()); } }