diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java index 5a95bea0..b1bffa96 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java @@ -22,20 +22,27 @@ package com.kingsrook.qqq.backend.module.rdbms; +import java.sql.Connection; +import com.kingsrook.qqq.backend.core.actions.QBackendTransaction; import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface; import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface; import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface; import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface; import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface; import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; +import com.kingsrook.qqq.backend.module.rdbms.actions.AbstractRDBMSAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSAggregateAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSCountAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSDeleteAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSInsertAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSQueryAction; +import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSTransaction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSUpdateAction; import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails; @@ -46,6 +53,10 @@ import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDe *******************************************************************************/ public class RDBMSBackendModule implements QBackendModuleInterface { + private static final QLogger LOG = QLogger.getLogger(RDBMSBackendModule.class); + + + /******************************************************************************* ** Method where a backend module must be able to provide its type (name). *******************************************************************************/ @@ -142,4 +153,24 @@ public class RDBMSBackendModule implements QBackendModuleInterface return (new RDBMSAggregateAction()); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QBackendTransaction openTransaction(AbstractTableActionInput input) throws QException + { + try + { + LOG.debug("Opening transaction"); + Connection connection = AbstractRDBMSAction.getConnection(input); + return (new RDBMSTransaction(connection)); + } + catch(Exception e) + { + throw new QException("Error opening transaction: " + e.getMessage(), e); + } + } + } diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java index 4aed4e14..ea3bba57 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java @@ -40,8 +40,6 @@ 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.QBackendTransaction; -import com.kingsrook.qqq.backend.core.actions.interfaces.QActionInterface; import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; @@ -87,7 +85,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; /******************************************************************************* ** Base class for all core actions in the RDBMS module. *******************************************************************************/ -public abstract class AbstractRDBMSAction implements QActionInterface +public abstract class AbstractRDBMSAction { private static final QLogger LOG = QLogger.getLogger(AbstractRDBMSAction.class); @@ -136,7 +134,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface /******************************************************************************* ** Get a database connection, per the backend in the request. *******************************************************************************/ - protected Connection getConnection(AbstractTableActionInput qTableRequest) throws SQLException + public static Connection getConnection(AbstractTableActionInput qTableRequest) throws SQLException { ConnectionManager connectionManager = new ConnectionManager(); return connectionManager.getConnection((RDBMSBackendMetaData) qTableRequest.getBackend()); @@ -826,27 +824,6 @@ public abstract class AbstractRDBMSAction implements QActionInterface - /******************************************************************************* - ** - *******************************************************************************/ - @Override - public QBackendTransaction openTransaction(AbstractTableActionInput input) throws QException - { - try - { - LOG.debug("Opening transaction"); - Connection connection = getConnection(input); - - return (new RDBMSTransaction(connection)); - } - catch(Exception e) - { - throw new QException("Error opening transaction: " + e.getMessage(), e); - } - } - - - /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java index 9dfc16ea..2a88d43e 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java @@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.module.rdbms.actions; import java.io.Serializable; import java.sql.Connection; -import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -38,7 +37,6 @@ 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.module.rdbms.jdbc.QueryManager; -import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; /******************************************************************************* @@ -56,25 +54,7 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte public InsertOutput execute(InsertInput insertInput) throws QException { InsertOutput rs = new InsertOutput(); - - if(CollectionUtils.nullSafeIsEmpty(insertInput.getRecords())) - { - LOG.debug("Insert request called with 0 records. Returning with no-op", logPair("tableName", insertInput.getTableName())); - rs.setRecords(new ArrayList<>()); - return (rs); - } - QTableMetaData table = insertInput.getTable(); - Instant now = Instant.now(); - - for(QRecord record : insertInput.getRecords()) - { - /////////////////////////////////////////// - // todo .. better (not hard-coded names) // - /////////////////////////////////////////// - setValueIfTableHasField(record, table, "createDate", now); - setValueIfTableHasField(record, table, "modifyDate", now); - } try { diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java index e53e5b8b..54276782 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java @@ -25,19 +25,18 @@ package com.kingsrook.qqq.backend.module.rdbms.actions; import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; -import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; +import java.util.Map; import java.util.stream.Collectors; import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface; +import com.kingsrook.qqq.backend.core.actions.tables.helpers.UpdateActionRecordSplitHelper; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; 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.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; @@ -66,60 +65,15 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte *******************************************************************************/ public UpdateOutput execute(UpdateInput updateInput) throws QException { - UpdateOutput rs = new UpdateOutput(); - - if(CollectionUtils.nullSafeIsEmpty(updateInput.getRecords())) - { - LOG.debug("Update request called with 0 records. Returning with no-op"); - rs.setRecords(new ArrayList<>()); - return (rs); - } - QTableMetaData table = updateInput.getTable(); - Instant now = Instant.now(); - List outputRecords = new ArrayList<>(); - rs.setRecords(outputRecords); + UpdateActionRecordSplitHelper updateActionRecordSplitHelper = new UpdateActionRecordSplitHelper(); + updateActionRecordSplitHelper.init(updateInput); - ///////////////////////////////////////////////////////////////////////////////////////////// - // we want to do batch updates. But, since we only update the columns that // - // are present in each record, it means we may have different update SQL for each // - // record. So, we will first "hash" up the records by their list of fields being updated. // - ///////////////////////////////////////////////////////////////////////////////////////////// - ListingHash, QRecord> recordsByFieldBeingUpdated = new ListingHash<>(); - boolean haveAnyWithoutErorrs = false; - for(QRecord record : updateInput.getRecords()) - { - //////////////////////////////////////////// - // todo .. better (not a hard-coded name) // - //////////////////////////////////////////// - setValueIfTableHasField(record, table, "modifyDate", now); + UpdateOutput rs = new UpdateOutput(); + rs.setRecords(updateActionRecordSplitHelper.getOutputRecords()); - 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())) - { - haveAnyWithoutErorrs = 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); - } - - if(!haveAnyWithoutErorrs) + if(!updateActionRecordSplitHelper.getHaveAnyWithoutErrors()) { LOG.info("Exiting early - all records have some error."); return (rs); @@ -144,9 +98,10 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte ///////////////////////////////////////////////////////////////////////////////////////////// // process each distinct list of fields being updated (e.g., each different SQL statement) // ///////////////////////////////////////////////////////////////////////////////////////////// - for(List fieldsBeingUpdated : recordsByFieldBeingUpdated.keySet()) + ListingHash, QRecord> recordsByFieldBeingUpdated = updateActionRecordSplitHelper.getRecordsByFieldBeingUpdated(); + for(Map.Entry, List> entry : recordsByFieldBeingUpdated.entrySet()) { - updateRecordsWithMatchingListOfFields(updateInput, connection, table, recordsByFieldBeingUpdated.get(fieldsBeingUpdated), fieldsBeingUpdated); + updateRecordsWithMatchingListOfFields(updateInput, connection, table, entry.getValue(), entry.getKey()); } } finally @@ -177,16 +132,7 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte // check for an optimization - if all of the records have the same values for // // all fields being updated, just do 1 update, with an IN list on the ids. // //////////////////////////////////////////////////////////////////////////////// - boolean allAreTheSame; - if(updateInput.getAreAllValuesBeingUpdatedTheSame() != null) - { - allAreTheSame = updateInput.getAreAllValuesBeingUpdatedTheSame(); - } - else - { - allAreTheSame = areAllValuesBeingUpdatedTheSame(recordList, fieldsBeingUpdated); - } - + boolean allAreTheSame = UpdateActionRecordSplitHelper.areAllValuesBeingUpdatedTheSame(updateInput, recordList, fieldsBeingUpdated); if(allAreTheSame) { updateRecordsWithMatchingValuesAndFields(updateInput, connection, table, recordList, fieldsBeingUpdated); @@ -312,43 +258,6 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte - /******************************************************************************* - ** - *******************************************************************************/ - private boolean areAllValuesBeingUpdatedTheSame(List recordList, List fieldsBeingUpdated) - { - if(recordList.size() == 1) - { - return (true); - } - - QRecord record0 = 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(record0.getValue(fieldName), record.getValue(fieldName))) - { - return (false); - } - } - } - - return (true); - } - - - /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java index f1bc1763..6a3cb3a8 100644 --- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java +++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java @@ -65,7 +65,7 @@ public class RDBMSInsertActionTest extends RDBMSActionTest { InsertInput insertInput = initInsertRequest(); insertInput.setRecords(null); - InsertOutput insertOutput = new RDBMSInsertAction().execute(insertInput); + InsertOutput insertOutput = new InsertAction().execute(insertInput); assertEquals(0, insertOutput.getRecords().size()); } @@ -79,7 +79,7 @@ public class RDBMSInsertActionTest extends RDBMSActionTest { InsertInput insertInput = initInsertRequest(); insertInput.setRecords(Collections.emptyList()); - InsertOutput insertOutput = new RDBMSInsertAction().execute(insertInput); + InsertOutput insertOutput = new InsertAction().execute(insertInput); assertEquals(0, insertOutput.getRecords().size()); } @@ -98,7 +98,7 @@ public class RDBMSInsertActionTest extends RDBMSActionTest .withValue("email", "jamestk@starfleet.net") .withValue("birthDate", "2210-05-20"); insertInput.setRecords(List.of(record)); - InsertOutput insertOutput = new RDBMSInsertAction().execute(insertInput); + InsertOutput insertOutput = new InsertAction().execute(insertInput); assertEquals(1, insertOutput.getRecords().size(), "Should return 1 row"); assertNotNull(insertOutput.getRecords().get(0).getValue("id"), "Should have an id in the row"); // todo - add errors to QRecord? assertTrue(insertResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); @@ -132,7 +132,7 @@ public class RDBMSInsertActionTest extends RDBMSActionTest .withValue("email", "doctor@starfleet.net") .withValue("birthDate", "2320-06-26"); insertInput.setRecords(List.of(record1, record2, record3)); - InsertOutput insertOutput = new RDBMSInsertAction().execute(insertInput); + InsertOutput insertOutput = new InsertAction().execute(insertInput); assertEquals(3, insertOutput.getRecords().size(), "Should return right # of rows"); assertEquals(6, insertOutput.getRecords().get(0).getValue("id"), "Should have next id in the row"); assertEquals(7, insertOutput.getRecords().get(1).getValue("id"), "Should have next id in the row"); diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java index 01c2c65c..99e7d067 100644 --- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java +++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java @@ -635,7 +635,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest insertInput.setTableName(TestUtils.TABLE_NAME_PERSON); InsertAction insertAction = new InsertAction(); - QBackendTransaction transaction = insertAction.openTransaction(insertInput); + QBackendTransaction transaction = QBackendTransaction.openFor(insertInput); insertInput.setTransaction(transaction); insertInput.setRecords(List.of(