diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/UpdateActionRecordSplitHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/UpdateActionRecordSplitHelper.java new file mode 100644 index 00000000..ac0acdcb --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/UpdateActionRecordSplitHelper.java @@ -0,0 +1,203 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 . + */ + +package com.kingsrook.qqq.backend.core.actions.tables.helpers; + + +import java.io.Serializable; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +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.ListingHash; + + +/******************************************************************************* + ** Helper for backends that want to do their updates on records grouped by the + ** set of fields that are being changed, and/or by the values those fields are + ** being set to. + ** + ** e.g., RDBMS, for n records where some sub-set of fields are all having values + ** set the same (say, a status=x), we can do that as 1 query where id in (?,?,...,?). + *******************************************************************************/ +public class UpdateActionRecordSplitHelper +{ + private ListingHash, QRecord> recordsByFieldBeingUpdated = new ListingHash<>(); + private boolean haveAnyWithoutErrors = false; + private List outputRecords = new ArrayList<>(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void init(UpdateInput updateInput) + { + QTableMetaData table = updateInput.getTable(); + Instant now = Instant.now(); + + for(QRecord record : updateInput.getRecords()) + { + //////////////////////////////////////////// + // todo .. better (not a hard-coded name) // + //////////////////////////////////////////// + setValueIfTableHasField(record, table, "modifyDate", now); + + List updatableFields = table.getFields().values().stream() + .map(QFieldMetaData::getName) + // todo - intent here is to avoid non-updateable fields - but this + // should be like based on field.isUpdatable once that attribute exists + .filter(name -> !name.equals("id")) + .filter(name -> record.getValues().containsKey(name)) + .toList(); + recordsByFieldBeingUpdated.add(updatableFields, record); + + if(CollectionUtils.nullSafeIsEmpty(record.getErrors())) + { + haveAnyWithoutErrors = true; + } + + ////////////////////////////////////////////////////////////////////////////// + // go ahead and put the record into the output list at this point in time, // + // so that the output list's order matches the input list order // + // note that if we want to capture updated values (like modify dates), then // + // we may want a map of primary key to output record, for easy updating. // + ////////////////////////////////////////////////////////////////////////////// + QRecord outputRecord = new QRecord(record); + outputRecords.add(outputRecord); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static boolean areAllValuesBeingUpdatedTheSame(UpdateInput updateInput, List recordList, List fieldsBeingUpdated) + { + if(updateInput.getAreAllValuesBeingUpdatedTheSame() != null) + { + //////////////////////////////////////////////////////////// + // if input told us what value to use here, then trust it // + //////////////////////////////////////////////////////////// + return (updateInput.getAreAllValuesBeingUpdatedTheSame()); + } + else + { + if(recordList.size() == 1) + { + ////////////////////////////////////////////////////// + // if a single record, then yes, that always counts // + ////////////////////////////////////////////////////// + return (true); + } + + /////////////////////////////////////////////////////////////////////// + // else iterate over the records, comparing them to the first record // + // return a false if any diffs are found. if no diffs, return true. // + /////////////////////////////////////////////////////////////////////// + QRecord firstRecord = recordList.get(0); + for(int i = 1; i < recordList.size(); i++) + { + QRecord record = recordList.get(i); + + if(CollectionUtils.nullSafeHasContents(record.getErrors())) + { + /////////////////////////////////////////////////////// + // skip records w/ errors (that we won't be updating // + /////////////////////////////////////////////////////// + continue; + } + + for(String fieldName : fieldsBeingUpdated) + { + if(!Objects.equals(firstRecord.getValue(fieldName), record.getValue(fieldName))) + { + return (false); + } + } + } + + return (true); + } + } + + + + /******************************************************************************* + ** If the table has a field with the given name, then set the given value in the + ** given record. + *******************************************************************************/ + protected void setValueIfTableHasField(QRecord record, QTableMetaData table, String fieldName, Serializable value) + { + try + { + if(table.getFields().containsKey(fieldName)) + { + record.setValue(fieldName, value); + } + } + catch(Exception e) + { + ///////////////////////////////////////////////// + // this means field doesn't exist, so, ignore. // + ///////////////////////////////////////////////// + } + } + + + + /******************************************************************************* + ** Getter for haveAnyWithoutErorrs + ** + *******************************************************************************/ + public boolean getHaveAnyWithoutErrors() + { + return haveAnyWithoutErrors; + } + + + + /******************************************************************************* + ** Getter for recordsByFieldBeingUpdated + ** + *******************************************************************************/ + public ListingHash, QRecord> getRecordsByFieldBeingUpdated() + { + return recordsByFieldBeingUpdated; + } + + + + /******************************************************************************* + ** Getter for outputRecords + ** + *******************************************************************************/ + public List getOutputRecords() + { + return outputRecords; + } +}