diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java index 0322a823..2f0b3106 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java @@ -365,26 +365,29 @@ public class PollingAutomationPerTableRunner implements Runnable QueryInput queryInput = new QueryInput(); queryInput.setTableName(table.getName()); + /////////////////////////////////////////////////////////////////////////////////////// + // set up a filter that is for the primary keys IN the list that we identified above // + /////////////////////////////////////////////////////////////////////////////////////// QQueryFilter filter = new QQueryFilter(); + filter.addCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, records.stream().map(r -> r.getValue(table.getPrimaryKeyField())).toList())); - ///////////////////////////////////////////////////////////////////////////////////////////////////// - // copy filter criteria from the action's filter to a new filter that we'll run here. // - // Critically - don't modify the filter object on the action! as that object has a long lifespan. // - ///////////////////////////////////////////////////////////////////////////////////////////////////// if(action.getFilter() != null) { - if(action.getFilter().getCriteria() != null) - { - action.getFilter().getCriteria().forEach(filter::addCriteria); - } + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // if the action defines a filter of its own, add that to the filter we'll run now as a sub-filter // + // not entirely clear if this needs to be a clone, but, it feels safe and cheap enough // + ///////////////////////////////////////////////////////////////////////////////////////////////////// + filter.addSubFilter(action.getFilter().clone()); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // we also want to set order-bys from the action into our filter (since they only apply at the top-level) // + //////////////////////////////////////////////////////////////////////////////////////////////////////////// if(action.getFilter().getOrderBys() != null) { action.getFilter().getOrderBys().forEach(filter::addOrderBy); } } - filter.addCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, records.stream().map(r -> r.getValue(table.getPrimaryKeyField())).toList())); - ///////////////////////////////////////////////////////////////////////////////////////////// // always add order-by the primary key, to give more predictable/consistent results // // todo - in future - if this becomes a source of slowness, make this a config to opt-out? // diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunnerTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunnerTest.java index d0c63edc..a0cb010d 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunnerTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunnerTest.java @@ -34,12 +34,16 @@ import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +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.model.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking; import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTrackingType; import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails; @@ -313,6 +317,56 @@ class PollingAutomationPerTableRunnerTest extends BaseTest + /******************************************************************************* + ** Test that sub-filters in filters work correctly to limit the records that get + ** applied (as at one point in time, they didn't!! + *******************************************************************************/ + @Test + void testFilterWithSubFilter() throws QException + { + QInstance qInstance = QContext.getQInstance(); + + /////////////////////////////////////////////////////////////////////////////////////////// + // update the CheckAge automation to have a sub-filter that should make Tim not be found // + /////////////////////////////////////////////////////////////////////////////////////////// + QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY); + TableAutomationAction checkAgeOnInsert = table.getAutomationDetails().getActions().stream() + .filter(a -> a.getName().equals("checkAgeOnInsert")) + .findFirst() + .orElseThrow(); + + QQueryFilter filter = checkAgeOnInsert.getFilter(); + filter.addSubFilter(new QQueryFilter(new QFilterCriteria("firstName", QCriteriaOperator.NOT_EQUALS, "Tim"))); + + //////////////////////////////////////////////////////////////////////// + // insert 2 person records - but only Darin should get the automation // + //////////////////////////////////////////////////////////////////////// + InsertInput insertInput = new InsertInput(); + insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + insertInput.setRecords(List.of( + new QRecord().withValue("id", 1).withValue("firstName", "Tim").withValue("birthDate", LocalDate.now()), + new QRecord().withValue("id", 2).withValue("firstName", "Darin").withValue("birthDate", LocalDate.now()))); + new InsertAction().execute(insertInput); + + runAllTableActions(qInstance); + + ///////////////////////////////// + // make sure Darin was updated // + ///////////////////////////////// + assertThat(TestUtils.queryTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .stream().filter(r -> r.getValueString("firstName").startsWith("Darin"))).first() + .matches(r -> r.getValueString("firstName").endsWith(TestUtils.CheckAge.SUFFIX_FOR_MINORS)); + + //////////////////////////////////// + // make sure Tim was not updated. // + //////////////////////////////////// + assertThat(TestUtils.queryTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .stream().filter(r -> r.getValueString("firstName").startsWith("Tim"))).first() + .matches(r -> !r.getValueString("firstName").endsWith(TestUtils.CheckAge.SUFFIX_FOR_MINORS)); + } + + + /******************************************************************************* ** *******************************************************************************/