diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java index 1f22d15a..5b7ddaa9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelper.java @@ -25,13 +25,18 @@ package com.kingsrook.qqq.backend.core.processes.utils; import java.io.Serializable; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; +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.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils; @@ -68,6 +73,12 @@ public class RecordLookupHelper String mapKey = tableName + "." + keyFieldName; Map recordMap = recordMaps.computeIfAbsent(mapKey, (k) -> new HashMap<>()); + //////////////////////////////////////////////////////////// + // make sure we have they key object in the expected type // + //////////////////////////////////////////////////////////// + QFieldType type = actionInput.getInstance().getTable(tableName).getField(keyFieldName).getType(); + key = ValueUtils.getValueAsFieldType(type, key); + if(!recordMap.containsKey(key)) { Optional optRecord = GeneralProcessUtils.getRecordByField(actionInput, tableName, keyFieldName, key); @@ -117,6 +128,38 @@ public class RecordLookupHelper + /******************************************************************************* + ** Optimization - to pre-load some records in a single IN-LIST query, + ** which would otherwise have to be looked up one-by-one - where - if the records + ** aren't found, then a null will be cached (for each element in the inList). + ** + *******************************************************************************/ + public void preloadRecords(String tableName, String keyFieldName, List inList) throws QException + { + if(CollectionUtils.nullSafeIsEmpty(inList)) + { + return; + } + + String mapKey = tableName + "." + keyFieldName; + Map tableMap = recordMaps.computeIfAbsent(mapKey, s -> new HashMap<>()); + + QQueryFilter filter = new QQueryFilter(new QFilterCriteria(keyFieldName, QCriteriaOperator.IN, inList)); + tableMap.putAll(GeneralProcessUtils.loadTableToMap(actionInput, tableName, keyFieldName, filter)); + + QFieldType type = actionInput.getInstance().getTable(tableName).getField(keyFieldName).getType(); + for(Serializable keyValue : inList) + { + if(!tableMap.containsKey(keyValue)) + { + keyValue = ValueUtils.getValueAsFieldType(type, keyValue); + tableMap.put(keyValue, null); + } + } + } + + + /******************************************************************************* ** Get a value from a record, by doing a lookup on the specified keyFieldName, ** for the specified key value. diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelperTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelperTest.java index 73f9a42c..ac6418cd 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelperTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/RecordLookupHelperTest.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.processes.utils; +import java.util.List; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; @@ -120,4 +121,37 @@ class RecordLookupHelperTest assertEquals(2, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN)); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testWithPreloadInListToCacheMisses() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + TestUtils.insertDefaultShapes(qInstance); + + RecordLookupHelper recordLookupHelper = new RecordLookupHelper(new AbstractActionInput(qInstance, new QSession())); + recordLookupHelper.preloadRecords(TestUtils.TABLE_NAME_SHAPE, "name", List.of("Triangle", "Square", "Circle", "Hexagon")); + assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN)); + + ////////////////////////////////////////////// + // assert we cached a record that was found // + ////////////////////////////////////////////// + assertNotNull(recordLookupHelper.getRecordByKey(TestUtils.TABLE_NAME_SHAPE, "name", "Triangle")); + assertEquals(1, recordLookupHelper.getRecordByKey(TestUtils.TABLE_NAME_SHAPE, "name", "Triangle").getValueInteger("id")); + assertEquals("Triangle", recordLookupHelper.getRecordByKey(TestUtils.TABLE_NAME_SHAPE, "name", "Triangle").getValueString("name")); + + ////////////////////////////////////////////////// + // assert we cached a null for a name not found // + ////////////////////////////////////////////////// + assertNull(recordLookupHelper.getRecordByKey(TestUtils.TABLE_NAME_SHAPE, "name", "Hexagon")); + + ///////////////////////////////////////////////////// + // all those gets should run no additional queries // + ///////////////////////////////////////////////////// + assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN)); + } + } \ No newline at end of file