mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Completed first round implementation of {pre,post}{insert,delete} actions
This commit is contained in:
@ -28,7 +28,23 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract class that a table can specify an implementation of, to provide
|
||||
** custom actions after a delete takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (ones which didn't
|
||||
** have a delete error), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records?
|
||||
** - possibly throwing an exception - though doing so won't stop the delete, and instead
|
||||
** will just set a warning on all of the deleted records...
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go back
|
||||
** to the caller - this is how errors and warnings are propagated .
|
||||
**
|
||||
** Note that the full deleteInput is available as a field in this class.
|
||||
**
|
||||
** A future enhancement here may be to take (as fields in this class) the list of
|
||||
** records that the delete action marked in error - the user might want to do
|
||||
** something special with them (idk, try some other way to delete them?)
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPostDeleteCustomizer
|
||||
{
|
||||
|
@ -28,7 +28,18 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract class that a table can specify an implementation of, to provide
|
||||
** custom actions after an insert takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the outputs of
|
||||
** the insert action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly throwing an exception - though doing so won't stop the update, and instead
|
||||
** will just set a warning on all of the updated records...
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go back to the caller.
|
||||
**
|
||||
** Note that the full insertInput is available as a field in this class.
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPostInsertCustomizer
|
||||
{
|
||||
|
@ -26,12 +26,27 @@ import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
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.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract class that a table can specify an implementation of, to provide
|
||||
** custom actions after an update takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the outputs of
|
||||
** the update action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records?
|
||||
** - possibly throwing an exception - though doing so won't stop the update, and instead
|
||||
** will just set a warning on all of the updated records...
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go back to the caller.
|
||||
**
|
||||
** Note that the full updateInput is available as a field in this class, and the
|
||||
** "old records" (e.g., with values freshly fetched from the backend) will be
|
||||
** available (if the backend supports it) - both as a list (`getOldRecordList`)
|
||||
** and as a memoized (by this class) map of primaryKey to record (`getOldRecordMap`).
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPostUpdateCustomizer
|
||||
{
|
||||
@ -45,7 +60,7 @@ public abstract class AbstractPostUpdateCustomizer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract List<QRecord> apply(List<QRecord> records);
|
||||
public abstract List<QRecord> apply(List<QRecord> records) throws QException;
|
||||
|
||||
|
||||
|
||||
|
@ -28,7 +28,24 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract class that a table can specify an implementation of, to provide
|
||||
** custom actions before a delete takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (which the DeleteAction
|
||||
** would look up based on the inputs to the delete action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly throwing an exception - if you really don't want the delete operation to continue.
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) - this is how errors
|
||||
** and warnings are propagated to the DeleteAction. Note that any records with
|
||||
** an error will NOT proceed to the backend's delete interface - but those with
|
||||
** warnings will.
|
||||
**
|
||||
** Note that the full deleteInput is available as a field in this class.
|
||||
**
|
||||
** A future enhancement here may be to take (as fields in this class) the list of
|
||||
** records that the delete action marked in error - the user might want to do
|
||||
** something special with them (idk, try some other way to delete them?)
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPreDeleteCustomizer
|
||||
{
|
||||
@ -62,4 +79,5 @@ public abstract class AbstractPreDeleteCustomizer
|
||||
{
|
||||
this.deleteInput = deleteInput;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,7 +28,18 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract class that a table can specify an implementation of, to provide
|
||||
** custom actions before an insert takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the inputs to
|
||||
** the insert action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly manipulating values (`setValue`)
|
||||
** - possibly throwing an exception - if you really don't want the insert operation to continue.
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go on to the backend implementation class.
|
||||
**
|
||||
** Note that the full insertInput is available as a field in this class.
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPreInsertCustomizer
|
||||
{
|
||||
|
@ -31,7 +31,21 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract class that a table can specify an implementation of, to provide
|
||||
** custom actions before an update takes place.
|
||||
**
|
||||
** General implementation would be, to iterate over the records (the inputs to
|
||||
** the update action), and look at their values:
|
||||
** - possibly adding Errors (`addError`) or Warnings (`addWarning`) to the records
|
||||
** - possibly manipulating values (`setValue`)
|
||||
** - possibly throwing an exception - if you really don't want the update operation to continue.
|
||||
** - doing "whatever else" you may want to do.
|
||||
** - returning the list of records (can be the input list) that you want to go on to the backend implementation class.
|
||||
**
|
||||
** Note that the full updateInput is available as a field in this class, and the
|
||||
** "old records" (e.g., with values freshly fetched from the backend) will be
|
||||
** available (if the backend supports it) - both as a list (`getOldRecordList`)
|
||||
** and as a memoized (by this class) map of primaryKey to record (`getOldRecordMap`).
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractPreUpdateCustomizer
|
||||
{
|
||||
|
@ -30,8 +30,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostDeleteCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreDeleteCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
@ -48,7 +50,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
@ -78,17 +79,24 @@ public class DeleteAction
|
||||
{
|
||||
ActionHelper.validateSession(deleteInput);
|
||||
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
|
||||
DeleteInterface deleteInterface = qModule.getDeleteInterface();
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
|
||||
{
|
||||
throw (new QException("A delete request may not contain both a list of primary keys and a query filter."));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// load the backend module and its delete interface //
|
||||
//////////////////////////////////////////////////////
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
|
||||
DeleteInterface deleteInterface = qModule.getDeleteInterface();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's a query filter, but the interface doesn't support using a query filter, then do a query for the filter, to get a list of primary keys instead //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(deleteInput.getQueryFilter() != null && !deleteInterface.supportsQueryFilterInput())
|
||||
{
|
||||
LOG.info("Querying for primary keys, for backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes");
|
||||
@ -105,16 +113,62 @@ public class DeleteAction
|
||||
}
|
||||
}
|
||||
|
||||
List<QRecord> recordListForAudit = deleteInterface.supportsPreFetchQuery() ? getRecordListForAuditIfNeeded(deleteInput) : new ArrayList<>();
|
||||
List<QRecord> recordsWithValidationErrors = deleteInterface.supportsPreFetchQuery() ? validateRecordsExistAndCanBeAccessed(deleteInput, recordListForAudit) : new ArrayList<>();
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// fetch the old list of records (if the backend supports it), for audits, //
|
||||
// for "not-found detection", and for the pre-action to use (if there is one) //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Optional<List<QRecord>> oldRecordList = fetchOldRecords(deleteInput, deleteInterface);
|
||||
|
||||
Optional<AbstractPreDeleteCustomizer> preDeleteCustomizer = QCodeLoader.getTableCustomizer(AbstractPreDeleteCustomizer.class, table, TableCustomizers.PRE_DELETE_RECORD.getRole());
|
||||
if(preDeleteCustomizer.isPresent())
|
||||
List<QRecord> recordsWithValidationErrors = new ArrayList<>();
|
||||
List<QRecord> recordsWithValidationWarnings = new ArrayList<>();
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
preDeleteCustomizer.get().setDeleteInput(deleteInput);
|
||||
preDeleteCustomizer.get().apply(null); // todo monday
|
||||
recordsWithValidationErrors = validateRecordsExistAndCanBeAccessed(deleteInput, oldRecordList.get());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-delete customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPreDeleteCustomizer> preDeleteCustomizer = QCodeLoader.getTableCustomizer(AbstractPreDeleteCustomizer.class, table, TableCustomizers.PRE_DELETE_RECORD.getRole());
|
||||
if(preDeleteCustomizer.isPresent() && oldRecordList.isPresent())
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// make list of records that are still good - to pass into the customizer //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> recordsForCustomizer = makeListOfRecordsNotInErrorList(primaryKeyField, oldRecordList.get(), recordsWithValidationErrors);
|
||||
|
||||
preDeleteCustomizer.get().setDeleteInput(deleteInput);
|
||||
List<QRecord> customizerResult = preDeleteCustomizer.get().apply(recordsForCustomizer);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// check if any records got errors in the customizer //
|
||||
///////////////////////////////////////////////////////
|
||||
Set<Serializable> primaryKeysToRemoveFromInput = new HashSet<>();
|
||||
for(QRecord record : customizerResult)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
recordsWithValidationErrors.add(record);
|
||||
primaryKeysToRemoveFromInput.add(record.getValue(primaryKeyField));
|
||||
}
|
||||
else
|
||||
{
|
||||
recordsWithValidationWarnings.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// do one mass removal of any bad keys from the input key list //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if(!primaryKeysToRemoveFromInput.isEmpty())
|
||||
{
|
||||
deleteInput.getPrimaryKeys().removeAll(primaryKeysToRemoveFromInput);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// have the backend do the delete //
|
||||
////////////////////////////////////
|
||||
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -126,18 +180,94 @@ public class DeleteAction
|
||||
deleteOutput.setRecordsWithErrors(new ArrayList<>());
|
||||
outputRecordsWithErrors = deleteOutput.getRecordsWithErrors();
|
||||
}
|
||||
|
||||
outputRecordsWithErrors.addAll(recordsWithValidationErrors);
|
||||
|
||||
List<QRecord> outputRecordsWithWarnings = deleteOutput.getRecordsWithWarnings();
|
||||
if(outputRecordsWithWarnings == null)
|
||||
{
|
||||
deleteOutput.setRecordsWithWarnings(new ArrayList<>());
|
||||
outputRecordsWithWarnings = deleteOutput.getRecordsWithWarnings();
|
||||
}
|
||||
outputRecordsWithWarnings.addAll(recordsWithValidationWarnings);
|
||||
|
||||
////////////////////////////////////////
|
||||
// delete associations, if applicable //
|
||||
////////////////////////////////////////
|
||||
manageAssociations(deleteInput);
|
||||
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(deleteInput).withRecordList(recordListForAudit));
|
||||
///////////////////////////////////
|
||||
// do the audit //
|
||||
// todo - add input.omitDmlAudit //
|
||||
///////////////////////////////////
|
||||
DMLAuditInput dmlAuditInput = new DMLAuditInput().withTableActionInput(deleteInput);
|
||||
oldRecordList.ifPresent(l -> dmlAuditInput.setRecordList(l));
|
||||
new DMLAuditAction().execute(dmlAuditInput);
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// finally, run the pre-delete customizer, if there is one //
|
||||
/////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPostDeleteCustomizer> postDeleteCustomizer = QCodeLoader.getTableCustomizer(AbstractPostDeleteCustomizer.class, table, TableCustomizers.POST_DELETE_RECORD.getRole());
|
||||
if(postDeleteCustomizer.isPresent() && oldRecordList.isPresent())
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// make list of records that are still good - to pass into the customizer //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> recordsForCustomizer = makeListOfRecordsNotInErrorList(primaryKeyField, oldRecordList.get(), outputRecordsWithErrors);
|
||||
|
||||
try
|
||||
{
|
||||
postDeleteCustomizer.get().setDeleteInput(deleteInput);
|
||||
List<QRecord> customizerResult = postDeleteCustomizer.get().apply(recordsForCustomizer);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// check if any records got errors in the customizer //
|
||||
///////////////////////////////////////////////////////
|
||||
for(QRecord record : customizerResult)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
outputRecordsWithErrors.add(record);
|
||||
}
|
||||
else if(CollectionUtils.nullSafeHasContents(record.getWarnings()))
|
||||
{
|
||||
outputRecordsWithWarnings.add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
for(QRecord record : recordsForCustomizer)
|
||||
{
|
||||
record.addWarning("An error occurred after the delete: " + e.getMessage());
|
||||
outputRecordsWithWarnings.add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deleteOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> makeListOfRecordsNotInErrorList(String primaryKeyField, List<QRecord> oldRecordList, List<QRecord> outputRecordsWithErrors)
|
||||
{
|
||||
Map<Serializable, QRecord> recordsWithErrorsMap = outputRecordsWithErrors.stream().collect(Collectors.toMap(r -> r.getValue(primaryKeyField), r -> r));
|
||||
List<QRecord> recordsForCustomizer = new ArrayList<>();
|
||||
for(QRecord record : oldRecordList)
|
||||
{
|
||||
if(!recordsWithErrorsMap.containsKey(record.getValue(primaryKeyField)))
|
||||
{
|
||||
recordsForCustomizer.add(record);
|
||||
}
|
||||
}
|
||||
return recordsForCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -183,12 +313,9 @@ public class DeleteAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> getRecordListForAuditIfNeeded(DeleteInput deleteInput) throws QException
|
||||
private static Optional<List<QRecord>> fetchOldRecords(DeleteInput deleteInput, DeleteInterface deleteInterface) throws QException
|
||||
{
|
||||
List<QRecord> recordListForAudit = null;
|
||||
|
||||
AuditLevel auditLevel = DMLAuditAction.getAuditLevel(deleteInput);
|
||||
if(AuditLevel.RECORD.equals(auditLevel) || AuditLevel.FIELD.equals(auditLevel))
|
||||
if(deleteInterface.supportsPreFetchQuery())
|
||||
{
|
||||
List<Serializable> primaryKeyList = deleteInput.getPrimaryKeys();
|
||||
if(CollectionUtils.nullSafeIsEmpty(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
|
||||
@ -198,19 +325,16 @@ public class DeleteAction
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(primaryKeyList))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// always fetch the records - we'll use them anyway for checking not-exist below //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(deleteInput.getTransaction());
|
||||
queryInput.setTableName(deleteInput.getTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(deleteInput.getTable().getPrimaryKeyField(), QCriteriaOperator.IN, primaryKeyList)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
recordListForAudit = queryOutput.getRecords();
|
||||
return (Optional.of(queryOutput.getRecords()));
|
||||
}
|
||||
}
|
||||
|
||||
return (recordListForAudit);
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCust
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
@ -89,13 +90,23 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
|
||||
setAutomationStatusField(insertInput);
|
||||
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||
//////////////////////////////////////////////////////
|
||||
// load the backend module and its insert interface //
|
||||
//////////////////////////////////////////////////////
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||
InsertInterface insertInterface = qModule.getInsertInterface();
|
||||
|
||||
/////////////////////////////
|
||||
// run standard validators //
|
||||
/////////////////////////////
|
||||
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
||||
setErrorsIfUniqueKeyErrors(insertInput, table);
|
||||
validateRequiredFields(insertInput);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-insert customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPreInsertCustomizer> preInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPreInsertCustomizer.class, table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
if(preInsertCustomizer.isPresent())
|
||||
{
|
||||
@ -103,15 +114,28 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
insertInput.setRecords(preInsertCustomizer.get().apply(insertInput.getRecords()));
|
||||
}
|
||||
|
||||
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
|
||||
List<String> errors = insertOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
|
||||
////////////////////////////////////
|
||||
// have the backend do the insert //
|
||||
////////////////////////////////////
|
||||
InsertOutput insertOutput = insertInterface.execute(insertInput);
|
||||
|
||||
//////////////////////////////
|
||||
// log if there were errors //
|
||||
//////////////////////////////
|
||||
List<String> errors = insertOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
LOG.warn("Errors in insertAction", logPair("tableName", table.getName()), logPair("errorCount", errors.size()), errors.size() < 10 ? logPair("errors", errors) : logPair("first10Errors", errors.subList(0, 10)));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// insert any associations in the input records //
|
||||
//////////////////////////////////////////////////
|
||||
manageAssociations(table, insertOutput.getRecords(), insertInput.getTransaction());
|
||||
|
||||
//////////////////
|
||||
// do the audit //
|
||||
//////////////////
|
||||
if(insertInput.getOmitDmlAudit())
|
||||
{
|
||||
LOG.debug("Requested to omit DML audit");
|
||||
@ -121,11 +145,24 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(insertOutput.getRecords()));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// finally, run the pre-insert customizer, if there is one //
|
||||
/////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPostInsertCustomizer> postInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPostInsertCustomizer.class, table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||
if(postInsertCustomizer.isPresent())
|
||||
{
|
||||
postInsertCustomizer.get().setInsertInput(insertInput);
|
||||
insertOutput.setRecords(postInsertCustomizer.get().apply(insertOutput.getRecords()));
|
||||
try
|
||||
{
|
||||
postInsertCustomizer.get().setInsertInput(insertInput);
|
||||
insertOutput.setRecords(postInsertCustomizer.get().apply(insertOutput.getRecords()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
for(QRecord record : insertOutput.getRecords())
|
||||
{
|
||||
record.addWarning("An error occurred after the insert: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return insertOutput;
|
||||
|
@ -57,7 +57,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
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.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
@ -92,57 +91,98 @@ public class UpdateAction
|
||||
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), table, updateInput.getRecords());
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// load the backend module and its update interface //
|
||||
//////////////////////////////////////////////////////
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
|
||||
UpdateInterface updateInterface = qModule.getUpdateInterface();
|
||||
|
||||
List<QRecord> oldRecordList = updateInterface.supportsPreFetchQuery() ? getOldRecordListForAuditIfNeeded(updateInput) : new ArrayList<>();
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// fetch the old list of records (if the backend supports it), for audits, //
|
||||
// for "not-found detection", and for the pre-action to use (if there is one) //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Optional<List<QRecord>> oldRecordList = fetchOldRecords(updateInput, updateInterface);
|
||||
|
||||
/////////////////////////////
|
||||
// run standard validators //
|
||||
/////////////////////////////
|
||||
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), table, updateInput.getRecords());
|
||||
validatePrimaryKeysAreGiven(updateInput);
|
||||
|
||||
if(updateInterface.supportsPreFetchQuery())
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList);
|
||||
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList.get());
|
||||
}
|
||||
|
||||
validateRequiredFields(updateInput);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-update customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPreUpdateCustomizer> preUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPreUpdateCustomizer.class, table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
preUpdateCustomizer.get().setUpdateInput(updateInput);
|
||||
preUpdateCustomizer.get().setOldRecordList(oldRecordList);
|
||||
oldRecordList.ifPresent(l -> preUpdateCustomizer.get().setOldRecordList(l));
|
||||
updateInput.setRecords(preUpdateCustomizer.get().apply(updateInput.getRecords()));
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// have the backend do the update //
|
||||
////////////////////////////////////
|
||||
UpdateOutput updateOutput = updateInterface.execute(updateInput);
|
||||
|
||||
//////////////////////////////
|
||||
// log if there were errors //
|
||||
//////////////////////////////
|
||||
List<String> errors = updateOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
LOG.warn("Errors in updateAction", logPair("tableName", updateInput.getTableName()), logPair("errorCount", errors.size()), errors.size() < 10 ? logPair("errors", errors) : logPair("first10Errors", errors.subList(0, 10)));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// update (inserting and deleting as needed) any associations in the input records //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
manageAssociations(updateInput);
|
||||
|
||||
//////////////////
|
||||
// do the audit //
|
||||
//////////////////
|
||||
if(updateInput.getOmitDmlAudit())
|
||||
{
|
||||
LOG.debug("Requested to omit DML audit");
|
||||
}
|
||||
else
|
||||
{
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(updateInput).withRecordList(updateOutput.getRecords()).withOldRecordList(oldRecordList));
|
||||
DMLAuditInput dmlAuditInput = new DMLAuditInput()
|
||||
.withTableActionInput(updateInput)
|
||||
.withRecordList(updateOutput.getRecords());
|
||||
oldRecordList.ifPresent(l -> dmlAuditInput.setOldRecordList(l));
|
||||
new DMLAuditAction().execute(dmlAuditInput);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// finally, run the pre-update customizer, if there is one //
|
||||
/////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPostUpdateCustomizer> postUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPostUpdateCustomizer.class, table, TableCustomizers.POST_UPDATE_RECORD.getRole());
|
||||
if(postUpdateCustomizer.isPresent())
|
||||
{
|
||||
postUpdateCustomizer.get().setUpdateInput(updateInput);
|
||||
postUpdateCustomizer.get().setOldRecordList(oldRecordList);
|
||||
updateOutput.setRecords(postUpdateCustomizer.get().apply(updateOutput.getRecords()));
|
||||
try
|
||||
{
|
||||
postUpdateCustomizer.get().setUpdateInput(updateInput);
|
||||
oldRecordList.ifPresent(l -> postUpdateCustomizer.get().setOldRecordList(l));
|
||||
updateOutput.setRecords(postUpdateCustomizer.get().apply(updateOutput.getRecords()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
for(QRecord record : updateOutput.getRecords())
|
||||
{
|
||||
record.addWarning("An error occurred after the update: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updateOutput;
|
||||
@ -150,6 +190,31 @@ public class UpdateAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Optional<List<QRecord>> fetchOldRecords(UpdateInput updateInput, UpdateInterface updateInterface) throws QException
|
||||
{
|
||||
if(updateInterface.supportsPreFetchQuery())
|
||||
{
|
||||
String primaryKeyField = updateInput.getTable().getPrimaryKeyField();
|
||||
List<Serializable> pkeysBeingUpdated = CollectionUtils.nonNullList(updateInput.getRecords()).stream().map(r -> r.getValue(primaryKeyField)).toList();
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(updateInput.getTransaction());
|
||||
queryInput.setTableName(updateInput.getTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, pkeysBeingUpdated)));
|
||||
// todo - need a limit? what if too many??
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
return (Optional.of(queryOutput.getRecords()));
|
||||
}
|
||||
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -383,45 +448,6 @@ public class UpdateAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> getOldRecordListForAuditIfNeeded(UpdateInput updateInput)
|
||||
{
|
||||
if(updateInput.getOmitDmlAudit())
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AuditLevel auditLevel = DMLAuditAction.getAuditLevel(updateInput);
|
||||
List<QRecord> oldRecordList = null;
|
||||
if(AuditLevel.FIELD.equals(auditLevel))
|
||||
{
|
||||
String primaryKeyField = updateInput.getTable().getPrimaryKeyField();
|
||||
List<Serializable> pkeysBeingUpdated = CollectionUtils.nonNullList(updateInput.getRecords()).stream().map(r -> r.getValue(primaryKeyField)).toList();
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(updateInput.getTransaction());
|
||||
queryInput.setTableName(updateInput.getTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, pkeysBeingUpdated)));
|
||||
// todo - need a limit? what if too many??
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
oldRecordList = queryOutput.getRecords();
|
||||
}
|
||||
return oldRecordList;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error getting old record list for audit", e, logPair("table", updateInput.getTableName()));
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** If the table being updated uses an automation-status field, populate it now.
|
||||
*******************************************************************************/
|
||||
|
@ -37,6 +37,7 @@ public class DeleteOutput extends AbstractActionOutput implements Serializable
|
||||
{
|
||||
private int deletedRecordCount = 0;
|
||||
private List<QRecord> recordsWithErrors;
|
||||
private List<QRecord> recordsWithWarnings;
|
||||
|
||||
|
||||
|
||||
@ -81,6 +82,7 @@ public class DeleteOutput extends AbstractActionOutput implements Serializable
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -94,6 +96,7 @@ public class DeleteOutput extends AbstractActionOutput implements Serializable
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -101,4 +104,50 @@ public class DeleteOutput extends AbstractActionOutput implements Serializable
|
||||
{
|
||||
deletedRecordCount += i;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordsWithWarnings
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecordsWithWarnings()
|
||||
{
|
||||
return (this.recordsWithWarnings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordsWithWarnings
|
||||
*******************************************************************************/
|
||||
public void setRecordsWithWarnings(List<QRecord> recordsWithWarnings)
|
||||
{
|
||||
this.recordsWithWarnings = recordsWithWarnings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordsWithWarnings
|
||||
*******************************************************************************/
|
||||
public DeleteOutput withRecordsWithWarnings(List<QRecord> recordsWithWarnings)
|
||||
{
|
||||
this.recordsWithWarnings = recordsWithWarnings;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addRecordWithWarning(QRecord recordWithWarning)
|
||||
{
|
||||
if(this.recordsWithWarnings == null)
|
||||
{
|
||||
this.recordsWithWarnings = new ArrayList<>();
|
||||
}
|
||||
this.recordsWithWarnings.add(recordWithWarning);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
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.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostDeleteCustomizer
|
||||
*******************************************************************************/
|
||||
class AbstractPostDeleteCustomizerTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
table.withCustomizer(TableCustomizers.POST_DELETE_RECORD.getRole(), new QCodeReference(AbstractPostDeleteCustomizerTest.PostDelete.class));
|
||||
|
||||
TestUtils.insertRecords(table, List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "Homer"),
|
||||
new QRecord().withValue("id", 2).withValue("firstName", "Marge"),
|
||||
new QRecord().withValue("id", 3).withValue("firstName", "Bart")
|
||||
));
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// try a delete that the post-customizer should reject //
|
||||
////////////////////////////////////////////////////////
|
||||
{
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
deleteInput.setPrimaryKeys(List.of(1, 2));
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
assertEquals(0, deleteOutput.getRecordsWithErrors().size());
|
||||
assertEquals(1, deleteOutput.getRecordsWithWarnings().size());
|
||||
assertEquals(1, deleteOutput.getRecordsWithWarnings().get(0).getValue("id"));
|
||||
assertEquals(2, deleteOutput.getDeletedRecordCount());
|
||||
assertEquals("You shouldn't have deleted Homer...", deleteOutput.getRecordsWithWarnings().get(0).getWarnings().get(0));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
getInput.setPrimaryKey(1);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertNull(getOutput.getRecord());
|
||||
|
||||
getInput.setPrimaryKey(2);
|
||||
getOutput = new GetAction().execute(getInput);
|
||||
assertNull(getOutput.getRecord());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PostDelete extends AbstractPostDeleteCustomizer
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records)
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getValue("firstName").equals("Homer"))
|
||||
{
|
||||
record.addWarning("You shouldn't have deleted Homer...");
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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 com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
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.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
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.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for AbstractPreUpdateCustomizer
|
||||
*******************************************************************************/
|
||||
class AbstractPostUpdateCustomizerTest extends BaseTest
|
||||
{
|
||||
private static final String NAME_CHANGES_TABLE = "nameChanges";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
|
||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
table.withCustomizer(TableCustomizers.POST_UPDATE_RECORD.getRole(), new QCodeReference(PostUpdate.class));
|
||||
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
|
||||
.withName(NAME_CHANGES_TABLE)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("personId", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("message", QFieldType.STRING)));
|
||||
|
||||
TestUtils.insertRecords(table, List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "Homer"),
|
||||
new QRecord().withValue("id", 2).withValue("firstName", "Marge"),
|
||||
new QRecord().withValue("id", 3).withValue("firstName", "Bart")
|
||||
));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// try an update where the post-update customizer will insert another record //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("firstName", "Homer J.")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertTrue(CollectionUtils.nullSafeIsEmpty(updateOutput.getRecords().get(0).getErrors()));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(NAME_CHANGES_TABLE);
|
||||
getInput.setPrimaryKey(1);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals(1, getOutput.getRecord().getValueInteger("personId"));
|
||||
assertEquals("Changed first name from [Homer] to [Homer J.]", getOutput.getRecord().getValueString("message"));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// try an update where the post-update customizer will issue a warning (though will have updated the record too) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("firstName", "Warning")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertTrue(CollectionUtils.nullSafeIsEmpty(updateOutput.getRecords().get(0).getErrors()));
|
||||
assertTrue(updateOutput.getRecords().get(0).getWarnings().stream().anyMatch(s -> s.contains("updated to a warning value")));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
getInput.setPrimaryKey(1);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals("Warning", getOutput.getRecord().getValueString("firstName"));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// try an update where the post-update customizer will throw an error (resulting in an updated record with a warning) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("firstName", "throw")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertTrue(CollectionUtils.nullSafeIsEmpty(updateOutput.getRecords().get(0).getErrors()));
|
||||
assertTrue(updateOutput.getRecords().get(0).getWarnings().stream().anyMatch(s -> s.contains("Forced Exception")));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
getInput.setPrimaryKey(1);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals("throw", getOutput.getRecord().getValueString("firstName"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PostUpdate extends AbstractPostUpdateCustomizer
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records) throws QException
|
||||
{
|
||||
List<QRecord> nameChangeRecordsToInsert = new ArrayList<>();
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
boolean recordHadError = CollectionUtils.nullSafeHasContents(record.getErrors());
|
||||
boolean inputRecordHadFirstName = record.getValues().containsKey("firstName");
|
||||
boolean inputRecordHadLastName = record.getValues().containsKey("lastName");
|
||||
|
||||
if(recordHadError)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(inputRecordHadFirstName)
|
||||
{
|
||||
QRecord oldRecord = getOldRecordMap().get(record.getValue("id"));
|
||||
if(oldRecord != null && oldRecord.getValue("firstName") != null)
|
||||
{
|
||||
nameChangeRecordsToInsert.add(new QRecord()
|
||||
.withValue("personId", record.getValue("id"))
|
||||
.withValue("message", "Changed first name from [" + oldRecord.getValueString("firstName") + "] to [" + record.getValueString("firstName") + "]")
|
||||
);
|
||||
}
|
||||
|
||||
if("warning".equalsIgnoreCase(record.getValueString("firstName")))
|
||||
{
|
||||
record.addWarning("Record was updated to a warning value");
|
||||
}
|
||||
|
||||
if("throw".equalsIgnoreCase(record.getValueString("firstName")))
|
||||
{
|
||||
throw (new QException("Forced Exception"));
|
||||
}
|
||||
}
|
||||
|
||||
if(inputRecordHadLastName)
|
||||
{
|
||||
QRecord oldRecord = getOldRecordMap().get(record.getValue("id"));
|
||||
if(oldRecord != null && oldRecord.getValue("lastName") != null)
|
||||
{
|
||||
nameChangeRecordsToInsert.add(new QRecord()
|
||||
.withValue("personId", record.getValue("id"))
|
||||
.withValue("message", "Changed last name from [" + oldRecord.getValueString("lastName") + "] to [" + record.getValueString("lastName") + "]")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(nameChangeRecordsToInsert))
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(NAME_CHANGES_TABLE);
|
||||
insertInput.setRecords(nameChangeRecordsToInsert);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
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.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for AbstractPreDeleteCustomizer
|
||||
*******************************************************************************/
|
||||
class AbstractPreDeleteCustomizerTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
table.withCustomizer(TableCustomizers.PRE_DELETE_RECORD.getRole(), new QCodeReference(PreDelete.class));
|
||||
|
||||
TestUtils.insertRecords(table, List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "Homer"),
|
||||
new QRecord().withValue("id", 2).withValue("firstName", "Marge"),
|
||||
new QRecord().withValue("id", 3).withValue("firstName", "Bart")
|
||||
));
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// try a delete that the pre-customizer should reject //
|
||||
////////////////////////////////////////////////////////
|
||||
{
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
deleteInput.setPrimaryKeys(List.of(1, 2));
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
assertEquals(1, deleteOutput.getRecordsWithErrors().size());
|
||||
assertEquals(0, deleteOutput.getRecordsWithWarnings().size());
|
||||
assertEquals(1, deleteOutput.getRecordsWithErrors().get(0).getValue("id"));
|
||||
assertEquals(1, deleteOutput.getDeletedRecordCount());
|
||||
assertEquals("You may not delete a Homer.", deleteOutput.getRecordsWithErrors().get(0).getErrors().get(0));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
getInput.setPrimaryKey(1);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals("Homer", getOutput.getRecord().getValueString("firstName"));
|
||||
|
||||
getInput.setPrimaryKey(2);
|
||||
getOutput = new GetAction().execute(getInput);
|
||||
assertNull(getOutput.getRecord());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PreDelete extends AbstractPreDeleteCustomizer
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records)
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getValue("firstName").equals("Homer"))
|
||||
{
|
||||
record.addError("You may not delete a Homer.");
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -24,12 +24,21 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
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.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
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.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -42,10 +51,74 @@ class AbstractPreUpdateCustomizerTest extends BaseTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test()
|
||||
void test() throws QException
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
table.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD.getRole(), new QCodeReference(PreUpdate.class));
|
||||
|
||||
TestUtils.insertRecords(table, List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "Homer"),
|
||||
new QRecord().withValue("id", 2).withValue("firstName", "Marge"),
|
||||
new QRecord().withValue("id", 3).withValue("firstName", "Bart")
|
||||
));
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// try an update that the pre-customizer should reject //
|
||||
/////////////////////////////////////////////////////////
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("firstName", "--")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertTrue(updateOutput.getRecords().get(0).getErrors().stream().anyMatch(s -> s.contains("must contain at least one letter")));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
getInput.setPrimaryKey(1);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals("Homer", getOutput.getRecord().getValueString("firstName"));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// try an update that gets its data changed //
|
||||
//////////////////////////////////////////////
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 2).withValue("firstName", "Ms.")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertTrue(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||
assertEquals("Ms.", updateOutput.getRecords().get(0).getValueString("firstName"));
|
||||
assertEquals("Simpson", updateOutput.getRecords().get(0).getValueString("lastName"));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
getInput.setPrimaryKey(2);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals("Ms.", getOutput.getRecord().getValueString("firstName"));
|
||||
assertEquals("Simpson", getOutput.getRecord().getValueString("lastName"));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// try an update that uses data from the previous version of the record //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 3).withValue("lastName", "Simpson")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertTrue(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||
assertEquals("BART", updateOutput.getRecords().get(0).getValueString("firstName"));
|
||||
assertEquals("Simpson", updateOutput.getRecords().get(0).getValueString("lastName"));
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
getInput.setPrimaryKey(3);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals("BART", getOutput.getRecord().getValueString("firstName"));
|
||||
assertEquals("Simpson", getOutput.getRecord().getValueString("lastName"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -53,7 +126,7 @@ class AbstractPreUpdateCustomizerTest extends BaseTest
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static class PreUpdate extends AbstractPreUpdateCustomizer
|
||||
public static class PreUpdate extends AbstractPreUpdateCustomizer
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -61,7 +134,44 @@ class AbstractPreUpdateCustomizerTest extends BaseTest
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records)
|
||||
{
|
||||
return null;
|
||||
for(QRecord record : records)
|
||||
{
|
||||
boolean inputRecordHadFirstName = record.getValues().containsKey("firstName");
|
||||
boolean inputRecordHadLastName = record.getValues().containsKey("lastName");
|
||||
|
||||
if(inputRecordHadFirstName)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// if updating first name, give an error if it has no letters //
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(!record.getValueString("firstName").matches(".*\\w.*"))
|
||||
{
|
||||
record.addError("First name must contain at least one letter.");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// if setting firstname to Ms., update last name to Simpson //
|
||||
//////////////////////////////////////////////////////////////
|
||||
if(record.getValueString("firstName").equals("Ms."))
|
||||
{
|
||||
record.setValue("lastName", "Simpson");
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// if updating the person's last name, set their first name to all caps //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
if(inputRecordHadLastName)
|
||||
{
|
||||
QRecord oldRecord = getOldRecordMap().get(record.getValue("id"));
|
||||
if(oldRecord != null && oldRecord.getValue("firstName") != null)
|
||||
{
|
||||
record.setValue("firstName", oldRecord.getValueString("firstName").toUpperCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ public class TestUtils
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void insertRecords(QInstance qInstance, QTableMetaData table, List<QRecord> records) throws QException
|
||||
public static void insertRecords(QTableMetaData table, List<QRecord> records) throws QException
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(table.getName());
|
||||
@ -286,6 +286,17 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated
|
||||
public static void insertRecords(QInstance qInstance, QTableMetaData table, List<QRecord> records) throws QException
|
||||
{
|
||||
insertRecords(table, records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user