Adding table-cacheOf concept; ability to add a child record from child-list widget

This commit is contained in:
2022-12-05 10:24:15 -06:00
parent 3691ad87e5
commit 060da69afb
32 changed files with 1766 additions and 109 deletions

View File

@ -69,8 +69,9 @@ class ChildRecordListRendererTest
void testParentRecordNotFound() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widget = ChildRecordListRenderer.defineWidgetFromJoin(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items");
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items")
.getWidgetMetaData();
qInstance.addWidget(widget);
RenderWidgetInput input = new RenderWidgetInput(qInstance);
@ -92,8 +93,9 @@ class ChildRecordListRendererTest
void testNoChildRecordsFound() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widget = ChildRecordListRenderer.defineWidgetFromJoin(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items");
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items")
.getWidgetMetaData();
qInstance.addWidget(widget);
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
@ -122,8 +124,9 @@ class ChildRecordListRendererTest
void testChildRecordsFound() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widget = ChildRecordListRenderer.defineWidgetFromJoin(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items");
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items")
.getWidgetMetaData();
qInstance.addWidget(widget);
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(

View File

@ -117,8 +117,9 @@ class ParentWidgetRendererTest
void testNoChildRecordsFound() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widget = ChildRecordListRenderer.defineWidgetFromJoin(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items");
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items")
.getWidgetMetaData();
qInstance.addWidget(widget);
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
@ -147,8 +148,9 @@ class ParentWidgetRendererTest
void testChildRecordsFound() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widget = ChildRecordListRenderer.defineWidgetFromJoin(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items");
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items")
.getWidgetMetaData();
qInstance.addWidget(widget);
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(

View File

@ -103,8 +103,9 @@ class ProcessWidgetRendererTest
void testNoChildRecordsFound() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widget = ChildRecordListRenderer.defineWidgetFromJoin(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items");
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items")
.getWidgetMetaData();
qInstance.addWidget(widget);
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
@ -133,8 +134,9 @@ class ProcessWidgetRendererTest
void testChildRecordsFound() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
QWidgetMetaData widget = ChildRecordListRenderer.defineWidgetFromJoin(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items");
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(qInstance.getJoin("orderLineItem"))
.withLabel("Line Items")
.getWidgetMetaData();
qInstance.addWidget(widget);
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(

View File

@ -22,12 +22,28 @@
package com.kingsrook.qqq.backend.core.actions.tables;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
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.session.QSession;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
@ -37,6 +53,19 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
class GetActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
@AfterEach
void beforeAndAfterEach()
{
MemoryRecordStore.getInstance().reset();
MemoryRecordStore.resetStatistics();
}
/*******************************************************************************
** At the core level, there isn't much that can be asserted, as it uses the
** mock implementation - just confirming that all of the "wiring" works.
@ -55,4 +84,164 @@ class GetActionTest
assertNotNull(result);
assertNotNull(result.getRecord());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testUniqueKeyCache() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
/////////////////////////////////////
// insert rows in the source table //
/////////////////////////////////////
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY), List.of(
new QRecord().withValue("id", 1).withValue("firstName", "George").withValue("lastName", "Washington").withValue("noOfShoes", 5),
new QRecord().withValue("id", 2).withValue("firstName", "John").withValue("lastName", "Adams"),
new QRecord().withValue("id", 3).withValue("firstName", "Thomas").withValue("lastName", "Jefferson")
));
/////////////////////////////////////////////////////////////////////////////
// get from the table which caches it - confirm they are (magically) found //
/////////////////////////////////////////////////////////////////////////////
{
GetInput getInput = new GetInput(qInstance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
getInput.setUniqueKey(Map.of("firstName", "George", "lastName", "Washington"));
GetOutput getOutput = new GetAction().execute(getInput);
assertNotNull(getOutput.getRecord());
assertNotNull(getOutput.getRecord().getValue("cachedDate"));
assertEquals(5, getOutput.getRecord().getValue("noOfShoes"));
}
///////////////////////////////////////////////////////////////////////////
// request a row that doesn't exist in cache or source, should miss both //
///////////////////////////////////////////////////////////////////////////
{
GetInput getInput = new GetInput(qInstance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
getInput.setUniqueKey(Map.of("firstName", "John", "lastName", "McCain"));
GetOutput getOutput = new GetAction().execute(getInput);
assertNull(getOutput.getRecord());
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// update the record in the source table - then re-get from cache table - shouldn't see new value. //
/////////////////////////////////////////////////////////////////////////////////////////////////////
{
UpdateInput updateInput = new UpdateInput(qInstance);
updateInput.setSession(new QSession());
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("noOfShoes", 6)));
new UpdateAction().execute(updateInput);
GetInput getInput = new GetInput(qInstance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
getInput.setUniqueKey(Map.of("firstName", "George", "lastName", "Washington"));
GetOutput getOutput = new GetAction().execute(getInput);
assertNotNull(getOutput.getRecord());
assertNotNull(getOutput.getRecord().getValue("cachedDate"));
assertEquals(5, getOutput.getRecord().getValue("noOfShoes"));
}
///////////////////////////////////////////////////////////////////////////
// delete the cached record; re-get, and we should see the updated value //
///////////////////////////////////////////////////////////////////////////
{
DeleteInput deleteInput = new DeleteInput(qInstance);
deleteInput.setSession(new QSession());
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
deleteInput.setQueryFilter(new QQueryFilter(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, "George")));
new DeleteAction().execute(deleteInput);
GetInput getInput = new GetInput(qInstance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
getInput.setUniqueKey(Map.of("firstName", "George", "lastName", "Washington"));
GetOutput getOutput = new GetAction().execute(getInput);
assertNotNull(getOutput.getRecord());
assertNotNull(getOutput.getRecord().getValue("cachedDate"));
assertEquals(6, getOutput.getRecord().getValue("noOfShoes"));
}
///////////////////////////////////////////////////////////////////
// update the source record; see that it isn't updated in cache. //
///////////////////////////////////////////////////////////////////
{
UpdateInput updateInput = new UpdateInput(qInstance);
updateInput.setSession(new QSession());
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("noOfShoes", 7)));
new UpdateAction().execute(updateInput);
GetInput getInput = new GetInput(qInstance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
getInput.setUniqueKey(Map.of("firstName", "George", "lastName", "Washington"));
GetOutput getOutput = new GetAction().execute(getInput);
assertNotNull(getOutput.getRecord());
assertNotNull(getOutput.getRecord().getValue("cachedDate"));
assertEquals(6, getOutput.getRecord().getValue("noOfShoes"));
///////////////////////////////////////////////////////////////////////
// then artificially move back the cachedDate in the cache table. //
// then re-get from cache table, and we should see the updated value //
///////////////////////////////////////////////////////////////////////
updateInput = new UpdateInput(qInstance);
updateInput.setSession(new QSession());
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
updateInput.setRecords(List.of(getOutput.getRecord().withValue("cachedDate", Instant.parse("2001-01-01T00:00:00Z"))));
new UpdateAction().execute(updateInput);
getOutput = new GetAction().execute(getInput);
assertEquals(7, getOutput.getRecord().getValue("noOfShoes"));
}
/////////////////////////////////////////////////
// should only be 1 cache record at this point //
/////////////////////////////////////////////////
assertEquals(1, TestUtils.queryTable(TestUtils.defineInstance(), TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE).size());
//////////////////////////////////////////////////////////////////////
// delete the source record - it will still be in the cache though. //
//////////////////////////////////////////////////////////////////////
{
DeleteInput deleteInput = new DeleteInput(qInstance);
deleteInput.setSession(new QSession());
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
deleteInput.setPrimaryKeys(List.of(1));
new DeleteAction().execute(deleteInput);
GetInput getInput = new GetInput(qInstance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
getInput.setUniqueKey(Map.of("firstName", "George", "lastName", "Washington"));
GetOutput getOutput = new GetAction().execute(getInput);
assertNotNull(getOutput.getRecord());
////////////////////////////////////////////////////////////////////
// then artificially move back the cachedDate in the cache table. //
// then re-get from cache table, and now it should go away //
////////////////////////////////////////////////////////////////////
UpdateInput updateInput = new UpdateInput(qInstance);
updateInput.setSession(new QSession());
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
updateInput.setRecords(List.of(getOutput.getRecord().withValue("cachedDate", Instant.parse("2001-01-01T00:00:00Z"))));
new UpdateAction().execute(updateInput);
getInput = new GetInput(qInstance);
getInput.setSession(new QSession());
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY_CACHE);
getInput.setUniqueKey(Map.of("firstName", "George", "lastName", "Washington"));
getOutput = new GetAction().execute(getInput);
assertNull(getOutput.getRecord());
}
}
}

View File

@ -1180,8 +1180,7 @@ class QInstanceValidatorTest
void testValidUniqueKeys()
{
assertValidationSuccess((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withUniqueKey(new UniqueKey().withFieldName("id"))
.withUniqueKey(new UniqueKey().withFieldName("firstName").withFieldName("lastName")));
.withUniqueKey(new UniqueKey().withFieldName("id")));
}

View File

@ -86,15 +86,19 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
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;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryTableBackendDetails;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule;
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
@ -133,6 +137,7 @@ public class TestUtils
public static final String PROCESS_NAME_RUN_SHAPES_PERSON_REPORT = "runShapesPersonReport";
public static final String TABLE_NAME_PERSON_FILE = "personFile";
public static final String TABLE_NAME_PERSON_MEMORY = "personMemory";
public static final String TABLE_NAME_PERSON_MEMORY_CACHE = "personMemoryCache";
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
public static final String TABLE_NAME_BASEPULL = "basepullTest";
public static final String REPORT_NAME_SHAPES_PERSON = "shapesPersonReport";
@ -164,6 +169,7 @@ public class TestUtils
qInstance.addTable(defineTablePerson());
qInstance.addTable(definePersonFileTable());
qInstance.addTable(definePersonMemoryTable());
qInstance.addTable(definePersonMemoryCacheTable());
qInstance.addTable(defineTableIdAndNameOnly());
qInstance.addTable(defineTableShape());
qInstance.addTable(defineTableBasepull());
@ -608,6 +614,7 @@ public class TestUtils
.withName(TABLE_NAME_PERSON_MEMORY)
.withBackendName(MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withUniqueKey(new UniqueKey("firstName", "lastName"))
.withFields(TestUtils.defineTablePerson().getFields()))
.withField(standardQqqAutomationStatusField())
@ -635,6 +642,36 @@ public class TestUtils
/*******************************************************************************
** Define yet another version of the 'person' table, also in-memory, and as a
** cache on the other in-memory one...
*******************************************************************************/
public static QTableMetaData definePersonMemoryCacheTable()
{
UniqueKey uniqueKey = new UniqueKey("firstName", "lastName");
return (new QTableMetaData()
.withName(TABLE_NAME_PERSON_MEMORY_CACHE)
.withBackendName(MEMORY_BACKEND_NAME)
.withBackendDetails(new MemoryTableBackendDetails()
.withCloneUponStore(true))
.withPrimaryKeyField("id")
.withUniqueKey(uniqueKey)
.withFields(TestUtils.defineTablePerson().getFields()))
.withField(new QFieldMetaData("cachedDate", QFieldType.DATE_TIME))
.withCacheOf(new CacheOf()
.withSourceTable(TABLE_NAME_PERSON_MEMORY)
.withCachedDateFieldName("cachedDate")
.withExpirationSeconds(60)
.withUseCase(new CacheUseCase()
.withType(CacheUseCase.Type.UNIQUE_KEY_TO_UNIQUE_KEY)
.withSourceUniqueKey(uniqueKey)
.withCacheUniqueKey(uniqueKey)
.withCacheSourceMisses(false)
));
}
/*******************************************************************************
**
*******************************************************************************/