Adding booleanOperator and subFilters to QQueryFilter

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

View File

@ -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.model.actions.AbstractTableActionInput;
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.metadata.fields.QFieldMetaData;
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.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
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<>();
for(QFilterCriteria criterion : criteria)
@ -175,13 +212,13 @@ public abstract class AbstractRDBMSAction implements QActionInterface
{
case EQUALS:
{
clause += " = ? ";
clause += " = ?";
expectedNoOfParams = 1;
break;
}
case NOT_EQUALS:
{
clause += " != ? ";
clause += " != ?";
expectedNoOfParams = 1;
break;
}
@ -196,7 +233,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
}
else
{
clause += " IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") ";
clause += " IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ")";
}
break;
}
@ -211,105 +248,105 @@ public abstract class AbstractRDBMSAction implements QActionInterface
}
else
{
clause += " NOT IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") ";
clause += " NOT IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ")";
}
break;
}
case STARTS_WITH:
{
clause += " LIKE ? ";
clause += " LIKE ?";
editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1;
break;
}
case ENDS_WITH:
{
clause += " LIKE ? ";
clause += " LIKE ?";
editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1;
break;
}
case CONTAINS:
{
clause += " LIKE ? ";
clause += " LIKE ?";
editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1;
break;
}
case NOT_STARTS_WITH:
{
clause += " NOT LIKE ? ";
clause += " NOT LIKE ?";
editFirstValue(values, (s -> s + "%"));
expectedNoOfParams = 1;
break;
}
case NOT_ENDS_WITH:
{
clause += " NOT LIKE ? ";
clause += " NOT LIKE ?";
editFirstValue(values, (s -> "%" + s));
expectedNoOfParams = 1;
break;
}
case NOT_CONTAINS:
{
clause += " NOT LIKE ? ";
clause += " NOT LIKE ?";
editFirstValue(values, (s -> "%" + s + "%"));
expectedNoOfParams = 1;
break;
}
case LESS_THAN:
{
clause += " < ? ";
clause += " < ?";
expectedNoOfParams = 1;
break;
}
case LESS_THAN_OR_EQUALS:
{
clause += " <= ? ";
clause += " <= ?";
expectedNoOfParams = 1;
break;
}
case GREATER_THAN:
{
clause += " > ? ";
clause += " > ?";
expectedNoOfParams = 1;
break;
}
case GREATER_THAN_OR_EQUALS:
{
clause += " >= ? ";
clause += " >= ?";
expectedNoOfParams = 1;
break;
}
case IS_BLANK:
{
clause += " IS NULL ";
clause += " IS NULL";
if(isString(field.getType()))
{
clause += " OR " + column + " = '' ";
clause += " OR " + column + " = ''";
}
expectedNoOfParams = 0;
break;
}
case IS_NOT_BLANK:
{
clause += " IS NOT NULL ";
clause += " IS NOT NULL";
if(isString(field.getType()))
{
clause += " AND " + column + " != '' ";
clause += " AND " + column + " != ''";
}
expectedNoOfParams = 0;
break;
}
case BETWEEN:
{
clause += " BETWEEN ? AND ? ";
clause += " BETWEEN ? AND ?";
expectedNoOfParams = 2;
break;
}
case NOT_BETWEEN:
{
clause += " NOT BETWEEN ? AND ? ";
clause += " NOT BETWEEN ? AND ?";
expectedNoOfParams = 2;
break;
}
@ -330,7 +367,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
params.addAll(values);
}
return (String.join(" AND ", clauses));
return (String.join(" " + booleanOperator.toString() + " ", clauses));
}

View File

@ -33,7 +33,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -62,9 +61,9 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
QQueryFilter filter = countInput.getFilter();
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

View File

@ -48,6 +48,7 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
private static final Logger LOG = LogManager.getLogger(RDBMSDeleteAction.class);
/*******************************************************************************
**
*******************************************************************************/
@ -258,7 +259,7 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
QTableMetaData table = deleteInput.getTable();
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?
String sql = "DELETE FROM "

View File

@ -76,9 +76,9 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
QQueryFilter filter = queryInput.getFilter();
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()))

View File

@ -519,7 +519,81 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.NOT_IN, List.of())));
queryOutput = new QueryAction().execute(queryInput);
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"));
}
}