Add preInsertOrUpdate, postInsertOrUpdate, and oldRecordListToMap

This commit is contained in:
2025-01-31 14:32:26 -06:00
parent f86b3d9973
commit 40b4b55bf4
2 changed files with 420 additions and 9 deletions

View File

@ -22,15 +22,19 @@
package com.kingsrook.qqq.backend.core.actions.customizers; package com.kingsrook.qqq.backend.core.actions.customizers;
import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface; import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; 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.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -47,7 +51,6 @@ public interface TableCustomizerInterface
{ {
QLogger LOG = QLogger.getLogger(TableCustomizerInterface.class); QLogger LOG = QLogger.getLogger(TableCustomizerInterface.class);
/******************************************************************************* /*******************************************************************************
** custom actions to run after a query (or get!) takes place. ** custom actions to run after a query (or get!) takes place.
** **
@ -77,8 +80,15 @@ public interface TableCustomizerInterface
*******************************************************************************/ *******************************************************************************/
default List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException default List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{ {
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName())); try
return (records); {
return (preInsertOrUpdate(insertInput, records, isPreview, Optional.empty()));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
} }
@ -104,8 +114,15 @@ public interface TableCustomizerInterface
*******************************************************************************/ *******************************************************************************/
default List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException default List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{ {
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName())); try
return (records); {
return (postInsertOrUpdate(insertInput, records, Optional.empty()));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
} }
@ -130,8 +147,15 @@ public interface TableCustomizerInterface
*******************************************************************************/ *******************************************************************************/
default List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException default List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{ {
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName())); try
return (records); {
return (preInsertOrUpdate(updateInput, records, isPreview, oldRecordList));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
} }
@ -151,8 +175,15 @@ public interface TableCustomizerInterface
*******************************************************************************/ *******************************************************************************/
default List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException default List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{ {
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName())); try
return (records); {
return (postInsertOrUpdate(updateInput, records, oldRecordList));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
} }
@ -199,4 +230,59 @@ public interface TableCustomizerInterface
return (records); return (records);
} }
/***************************************************************************
** Optional method to override in a customizer that does the same thing for
** both preInsert & preUpdate.
***************************************************************************/
default List<QRecord> preInsertOrUpdate(AbstractActionInput input, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
throw NotImplementedHereException.instance;
}
/***************************************************************************
** Optional method to override in a customizer that does the same thing for
** both postInsert & postUpdate.
***************************************************************************/
default List<QRecord> postInsertOrUpdate(AbstractActionInput input, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
throw NotImplementedHereException.instance;
}
/***************************************************************************
**
***************************************************************************/
default Optional<Map<Serializable, QRecord>> oldRecordListToMap(String primaryKeyField, Optional<List<QRecord>> oldRecordList)
{
if(oldRecordList.isPresent())
{
return (Optional.of(CollectionUtils.listToMap(oldRecordList.get(), r -> r.getValue(primaryKeyField))));
}
else
{
return (Optional.empty());
}
}
}
/***************************************************************************
**
***************************************************************************/
class NotImplementedHereException extends QException
{
static NotImplementedHereException instance = new NotImplementedHereException();
/***************************************************************************
**
***************************************************************************/
public NotImplementedHereException()
{
super("Not implemented here");
}
} }

View File

@ -0,0 +1,325 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.customizers;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
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.code.QCodeReference;
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.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Unit test for TableCustomizerInterface
*******************************************************************************/
class TableCustomizerInterfaceTest extends BaseTest
{
private static List<String> events = new ArrayList<>();
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
@AfterEach
void beforeAndAfterEach()
{
events.clear();
QLogger.deactivateCollectingLoggerForClass(TableCustomizerInterface.class);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPreInsertOnly() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(PreInsertOnly.class))
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PreInsertOnly.class));
reInitInstanceInContext(qInstance);
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class);
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1).contains("PreInsertOnly.preInsert()");
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1);
assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of preUpdate is running");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPreUpdateOnly() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PreUpdateOnly.class))
.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(PreUpdateOnly.class));
reInitInstanceInContext(qInstance);
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class);
new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1).contains("PreUpdateOnly.preUpdate()");
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1);
assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of preInsert is running");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPreInsertOrUpdateOnly() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PreInsertOrUpdateOnly.class))
.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(PreInsertOrUpdateOnly.class));
reInitInstanceInContext(qInstance);
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class);
new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1).contains("PreInsertOrUpdateOnly.preInsertOrUpdate()");
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(2).allMatch(s -> s.contains("PreInsertOrUpdateOnly.preInsertOrUpdate()"));
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPostInsertOnly() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PostInsertOnly.class))
.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PostInsertOnly.class));
reInitInstanceInContext(qInstance);
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class);
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1).contains("PostInsertOnly.postInsert()");
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1);
assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of postUpdate is running");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPostUpdateOnly() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PostUpdateOnly.class))
.withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PostUpdateOnly.class));
reInitInstanceInContext(qInstance);
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class);
new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1).contains("PostUpdateOnly.postUpdate()");
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1);
assertThat(collectingLogger.getCollectedMessages()).hasSize(1).element(0).extracting("message").asString().contains("A default implementation of postInsert is running");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPostInsertOrUpdateOnly() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(PostInsertOrUpdateOnly.class))
.withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(PostInsertOrUpdateOnly.class));
reInitInstanceInContext(qInstance);
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(TableCustomizerInterface.class);
new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(1).contains("PostInsertOrUpdateOnly.postInsertOrUpdate()");
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord()));
assertThat(events).hasSize(2).allMatch(s -> s.contains("PostInsertOrUpdateOnly.postInsertOrUpdate()"));
assertThat(collectingLogger.getCollectedMessages()).hasSize(0);
}
/***************************************************************************
**
***************************************************************************/
public static class PreInsertOnly implements TableCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
events.add("PreInsertOnly.preInsert()");
return (records);
}
}
/***************************************************************************
**
***************************************************************************/
public static class PreUpdateOnly implements TableCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
events.add("PreUpdateOnly.preUpdate()");
return (records);
}
}
/***************************************************************************
**
***************************************************************************/
public static class PreInsertOrUpdateOnly implements TableCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> preInsertOrUpdate(AbstractActionInput input, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
events.add("PreInsertOrUpdateOnly.preInsertOrUpdate()");
return (records);
}
}
/***************************************************************************
**
***************************************************************************/
public static class PostInsertOnly implements TableCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
events.add("PostInsertOnly.postInsert()");
return (records);
}
}
/***************************************************************************
**
***************************************************************************/
public static class PostUpdateOnly implements TableCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
events.add("PostUpdateOnly.postUpdate()");
return (records);
}
}
/***************************************************************************
**
***************************************************************************/
public static class PostInsertOrUpdateOnly implements TableCustomizerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public List<QRecord> postInsertOrUpdate(AbstractActionInput input, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
events.add("PostInsertOrUpdateOnly.postInsertOrUpdate()");
return (records);
}
}
}