mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Add LoadViaInsertOrUpdateStep; make PVS field labels not have Id suffix; add populateFromQRecord
This commit is contained in:
@ -79,6 +79,11 @@ public class QInstanceEnricher
|
|||||||
|
|
||||||
private final QInstance qInstance;
|
private final QInstance qInstance;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// todo - come up w/ a way for app devs to set configs! //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
private boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -229,7 +234,14 @@ public class QInstanceEnricher
|
|||||||
{
|
{
|
||||||
if(!StringUtils.hasContent(field.getLabel()))
|
if(!StringUtils.hasContent(field.getLabel()))
|
||||||
{
|
{
|
||||||
field.setLabel(nameToLabel(field.getName()));
|
if(configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels && StringUtils.hasContent(field.getPossibleValueSourceName()) && field.getName() != null && field.getName().endsWith("Id"))
|
||||||
|
{
|
||||||
|
field.setLabel(nameToLabel(field.getName().substring(0, field.getName().length() - 2)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
field.setLabel(nameToLabel(field.getName()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -60,16 +60,32 @@ public abstract class QRecordEntity
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
T entity = c.getConstructor().newInstance();
|
T entity = c.getConstructor().newInstance();
|
||||||
|
entity.populateFromQRecord(qRecord);
|
||||||
|
return (entity);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QException("Error building entity from qRecord.", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<QRecordEntityField> fieldList = getFieldList(c);
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Build an entity of this QRecord type from a QRecord
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected <T extends QRecordEntity> void populateFromQRecord(QRecord qRecord) throws QException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
|
||||||
for(QRecordEntityField qRecordEntityField : fieldList)
|
for(QRecordEntityField qRecordEntityField : fieldList)
|
||||||
{
|
{
|
||||||
Serializable value = qRecord.getValue(qRecordEntityField.getFieldName());
|
Serializable value = qRecord.getValue(qRecordEntityField.getFieldName());
|
||||||
Object typedValue = qRecordEntityField.convertValueType(value);
|
Object typedValue = qRecordEntityField.convertValueType(value);
|
||||||
qRecordEntityField.getSetter().invoke(entity, typedValue);
|
qRecordEntityField.getSetter().invoke(this, typedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (entity);
|
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. 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.processes.implementations.etl.streamedwithfrontend;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
|
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.model.actions.processes.RunBackendStepInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Generic implementation of a LoadStep - that runs Insert and/or Update
|
||||||
|
** actions for the destination table - where the presence or absence of the
|
||||||
|
** record's primaryKey field is the indicator for which to do. e.g., it assumes
|
||||||
|
** auto-generated ids, to be populated upon insert.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class LoadViaInsertOrUpdateStep extends AbstractLoadStep
|
||||||
|
{
|
||||||
|
public static final String FIELD_DESTINATION_TABLE = "destinationTable";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Execute the backend step - using the request as input, and the result as output.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||||
|
{
|
||||||
|
QTableMetaData tableMetaData = runBackendStepInput.getInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
|
||||||
|
List<QRecord> recordsToInsert = new ArrayList<>();
|
||||||
|
List<QRecord> recordsToUpdate = new ArrayList<>();
|
||||||
|
for(QRecord record : runBackendStepInput.getRecords())
|
||||||
|
{
|
||||||
|
if(record.getValue(tableMetaData.getPrimaryKeyField()) == null)
|
||||||
|
{
|
||||||
|
recordsToInsert.add(record);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recordsToUpdate.add(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!recordsToInsert.isEmpty())
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput(runBackendStepInput.getInstance());
|
||||||
|
insertInput.setSession(runBackendStepInput.getSession());
|
||||||
|
insertInput.setTableName(tableMetaData.getName());
|
||||||
|
insertInput.setRecords(runBackendStepInput.getRecords());
|
||||||
|
getTransaction().ifPresent(insertInput::setTransaction);
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
|
runBackendStepOutput.getRecords().addAll(insertOutput.getRecords());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!recordsToUpdate.isEmpty())
|
||||||
|
{
|
||||||
|
UpdateInput updateInput = new UpdateInput(runBackendStepInput.getInstance());
|
||||||
|
updateInput.setSession(runBackendStepInput.getSession());
|
||||||
|
updateInput.setTableName(tableMetaData.getName());
|
||||||
|
updateInput.setRecords(runBackendStepInput.getRecords());
|
||||||
|
getTransaction().ifPresent(updateInput::setTransaction);
|
||||||
|
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||||
|
runBackendStepOutput.getRecords().addAll(updateOutput.getRecords());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public Optional<QBackendTransaction> openTransaction(RunBackendStepInput runBackendStepInput) throws QException
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput(runBackendStepInput.getInstance());
|
||||||
|
insertInput.setSession(runBackendStepInput.getSession());
|
||||||
|
insertInput.setTableName(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
|
||||||
|
|
||||||
|
return (Optional.of(new InsertAction().openTransaction(insertInput)));
|
||||||
|
}
|
||||||
|
}
|
@ -80,10 +80,11 @@ public class StreamedETLWithFrontendProcess
|
|||||||
public static final String FIELD_VALIDATION_SUMMARY = "validationSummary"; // List<ProcessSummaryLine>
|
public static final String FIELD_VALIDATION_SUMMARY = "validationSummary"; // List<ProcessSummaryLine>
|
||||||
public static final String FIELD_PROCESS_SUMMARY = "processResults"; // List<ProcessSummaryLine>
|
public static final String FIELD_PROCESS_SUMMARY = "processResults"; // List<ProcessSummaryLine>
|
||||||
|
|
||||||
public static final String DEFAULT_PREVIEW_MESSAGE_FOR_INSERT = "This is a preview of the records that will be created.";
|
public static final String DEFAULT_PREVIEW_MESSAGE_FOR_INSERT = "This is a preview of the records that will be created.";
|
||||||
public static final String DEFAULT_PREVIEW_MESSAGE_FOR_UPDATE = "This is a preview of the records that will be updated.";
|
public static final String DEFAULT_PREVIEW_MESSAGE_FOR_UPDATE = "This is a preview of the records that will be updated.";
|
||||||
public static final String DEFAULT_PREVIEW_MESSAGE_FOR_DELETE = "This is a preview of the records that will be deleted.";
|
public static final String DEFAULT_PREVIEW_MESSAGE_FOR_INSERT_OR_UPDATE = "This is a preview of the records that will be inserted or updated.";
|
||||||
public static final String FIELD_PREVIEW_MESSAGE = "previewMessage";
|
public static final String DEFAULT_PREVIEW_MESSAGE_FOR_DELETE = "This is a preview of the records that will be deleted.";
|
||||||
|
public static final String FIELD_PREVIEW_MESSAGE = "previewMessage";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -245,32 +245,32 @@ public class GenerateReportActionTest
|
|||||||
Map<String, String> row = iterator.next();
|
Map<String, String> row = iterator.next();
|
||||||
assertEquals(6, list.size());
|
assertEquals(6, list.size());
|
||||||
|
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
assertThat(row.get("Home State")).isEqualTo("IL");
|
||||||
assertThat(row.get("Last Name")).isEqualTo("Jonson");
|
assertThat(row.get("Last Name")).isEqualTo("Jonson");
|
||||||
assertThat(row.get("Quantity")).isNull();
|
assertThat(row.get("Quantity")).isNull();
|
||||||
|
|
||||||
row = iterator.next();
|
row = iterator.next();
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
assertThat(row.get("Home State")).isEqualTo("IL");
|
||||||
assertThat(row.get("Last Name")).isEqualTo("Jones");
|
assertThat(row.get("Last Name")).isEqualTo("Jones");
|
||||||
assertThat(row.get("Quantity")).isEqualTo("3");
|
assertThat(row.get("Quantity")).isEqualTo("3");
|
||||||
|
|
||||||
row = iterator.next();
|
row = iterator.next();
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
assertThat(row.get("Home State")).isEqualTo("IL");
|
||||||
assertThat(row.get("Last Name")).isEqualTo("Kelly");
|
assertThat(row.get("Last Name")).isEqualTo("Kelly");
|
||||||
assertThat(row.get("Quantity")).isEqualTo("4");
|
assertThat(row.get("Quantity")).isEqualTo("4");
|
||||||
|
|
||||||
row = iterator.next();
|
row = iterator.next();
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
assertThat(row.get("Home State")).isEqualTo("IL");
|
||||||
assertThat(row.get("Last Name")).isEqualTo("Keller");
|
assertThat(row.get("Last Name")).isEqualTo("Keller");
|
||||||
assertThat(row.get("Quantity")).isEqualTo("5");
|
assertThat(row.get("Quantity")).isEqualTo("5");
|
||||||
|
|
||||||
row = iterator.next();
|
row = iterator.next();
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
assertThat(row.get("Home State")).isEqualTo("IL");
|
||||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||||
assertThat(row.get("Quantity")).isEqualTo("6");
|
assertThat(row.get("Quantity")).isEqualTo("6");
|
||||||
|
|
||||||
row = iterator.next();
|
row = iterator.next();
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("MO");
|
assertThat(row.get("Home State")).isEqualTo("MO");
|
||||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||||
assertThat(row.get("Quantity")).isEqualTo("7");
|
assertThat(row.get("Quantity")).isEqualTo("7");
|
||||||
}
|
}
|
||||||
@ -299,12 +299,12 @@ public class GenerateReportActionTest
|
|||||||
Iterator<Map<String, String>> iterator = list.iterator();
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
Map<String, String> row = iterator.next();
|
Map<String, String> row = iterator.next();
|
||||||
assertEquals(2, list.size());
|
assertEquals(2, list.size());
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("MO");
|
assertThat(row.get("Home State")).isEqualTo("MO");
|
||||||
assertThat(row.get("Last Name")).isNull();
|
assertThat(row.get("Last Name")).isNull();
|
||||||
assertThat(row.get("Quantity")).isEqualTo("7");
|
assertThat(row.get("Quantity")).isEqualTo("7");
|
||||||
|
|
||||||
row = iterator.next();
|
row = iterator.next();
|
||||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
assertThat(row.get("Home State")).isEqualTo("IL");
|
||||||
assertThat(row.get("Last Name")).isNull();
|
assertThat(row.get("Last Name")).isNull();
|
||||||
assertThat(row.get("Quantity")).isEqualTo("18");
|
assertThat(row.get("Quantity")).isEqualTo("18");
|
||||||
}
|
}
|
||||||
|
@ -108,6 +108,23 @@ class QInstanceEnricherTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Test that a field missing a label gets the default label applied (name w/ UC-first)
|
||||||
|
** w/ Id stripped from the end, because it's a PVS
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_nullFieldLabelComesFromNameWithoutIdForPossibleValues()
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QFieldMetaData homeStateIdField = qInstance.getTable("person").getField("homeStateId");
|
||||||
|
assertNull(homeStateIdField.getLabel());
|
||||||
|
new QInstanceEnricher(qInstance).enrich();
|
||||||
|
assertEquals("Home State", homeStateIdField.getLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Test that a fieldSection missing a label gets the default label applied (name w/ UC-first).
|
** Test that a fieldSection missing a label gets the default label applied (name w/ UC-first).
|
||||||
**
|
**
|
||||||
|
@ -111,6 +111,45 @@ public class StreamedETLWithFrontendProcessTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testLoadViaInsertOrUpdate() throws QException
|
||||||
|
{
|
||||||
|
QInstance instance = TestUtils.defineInstance();
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// define the process - an ELT from Shapes to Shapes - inserting 1, updating 2 //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QProcessMetaData process = StreamedETLWithFrontendProcess.defineProcessMetaData(
|
||||||
|
TestUtils.TABLE_NAME_SHAPE,
|
||||||
|
TestUtils.TABLE_NAME_SHAPE,
|
||||||
|
ExtractViaQueryStep.class,
|
||||||
|
TestTransformShapeToMaybeNewShape.class,
|
||||||
|
LoadViaInsertOrUpdateStep.class);
|
||||||
|
process.setName("test");
|
||||||
|
process.setTableName(TestUtils.TABLE_NAME_SHAPE);
|
||||||
|
instance.addProcess(process);
|
||||||
|
|
||||||
|
TestUtils.insertDefaultShapes(instance);
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// run the process //
|
||||||
|
/////////////////////
|
||||||
|
runProcess(instance, process);
|
||||||
|
|
||||||
|
List<QRecord> postList = TestUtils.queryTable(instance, TestUtils.TABLE_NAME_SHAPE);
|
||||||
|
assertEquals(4, postList.size());
|
||||||
|
assertThat(postList)
|
||||||
|
.as("Should have inserted a new Square").anyMatch(qr -> qr.getValue("name").equals("a new Square"))
|
||||||
|
.as("Should have left old Square alone").anyMatch(qr -> qr.getValue("name").equals("Square"))
|
||||||
|
.as("Should have updated Triangle").anyMatch(qr -> qr.getValue("name").equals("an updated Triangle"))
|
||||||
|
.as("Should have updated Circle").anyMatch(qr -> qr.getValue("name").equals("an updated Circle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -368,6 +407,49 @@ public class StreamedETLWithFrontendProcessTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static class TestTransformShapeToMaybeNewShape extends AbstractTransformStep
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Execute the backend step - using the request as input, and the result as output.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||||
|
{
|
||||||
|
for(QRecord qRecord : runBackendStepInput.getRecords())
|
||||||
|
{
|
||||||
|
String name = qRecord.getValueString("name");
|
||||||
|
if(name.equals("Square"))
|
||||||
|
{
|
||||||
|
QRecord toInsertRecord = new QRecord();
|
||||||
|
toInsertRecord.setValue("name", "a new Square");
|
||||||
|
runBackendStepOutput.getRecords().add(toInsertRecord);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QRecord toUpdateRecord = new QRecord();
|
||||||
|
toUpdateRecord.setValue("id", qRecord.getValueInteger("id"));
|
||||||
|
toUpdateRecord.setValue("name", "an updated " + name);
|
||||||
|
runBackendStepOutput.getRecords().add(toUpdateRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput runBackendStepOutput, boolean isForResultScreen)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
Reference in New Issue
Block a user