From d3573614420c9e9026e0a1d49bbfdb307d48f20b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 1 Mar 2022 18:28:27 -0600 Subject: [PATCH] Checkpoint: Added Update (edit) action --- .../module/rdbms/RDBMSBackendModule.java | 12 ++ .../rdbms/actions/RDBMSDeleteAction.java | 2 +- .../rdbms/actions/RDBMSInsertAction.java | 1 - .../rdbms/actions/RDBMSQueryAction.java | 5 +- .../rdbms/actions/RDBMSUpdateAction.java | 96 ++++++++++++ .../rdbms/actions/RDBMSUpdateActionTest.java | 139 ++++++++++++++++++ 6 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java create mode 100644 src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java index 767fd058..7c30ee17 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java @@ -9,9 +9,11 @@ import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface; +import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface; 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.RDBMSUpdateAction; /******************************************************************************* @@ -40,6 +42,16 @@ public class RDBMSBackendModule implements QBackendModuleInterface } + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public UpdateInterface getUpdateInterface() + { + return (new RDBMSUpdateAction()); + } + + /******************************************************************************* ** diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java index f96f5bd5..a85aaecc 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java @@ -59,7 +59,7 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte rs.setRecords(recordsWithStatus); for(Serializable primaryKey : deleteRequest.getPrimaryKeys()) { - QRecord qRecord = new QRecord().withTableName(deleteRequest.getTableName()).withPrimaryKey(primaryKey); + QRecord qRecord = new QRecord().withTableName(deleteRequest.getTableName()).withValue("id", primaryKey); // todo uh, identify any errors? QRecordWithStatus recordWithStatus = new QRecordWithStatus(qRecord); recordsWithStatus.add(recordWithStatus); diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java index f95055e8..fe064d35 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java @@ -86,7 +86,6 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte { Integer id = idList.get(index++); QRecordWithStatus recordWithStatus = new QRecordWithStatus(record); - recordWithStatus.setPrimaryKey(id); recordWithStatus.setValue(table.getPrimaryKeyField(), id); recordsWithStatus.add(recordWithStatus); } diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java index 2cc38c92..88f774ab 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java @@ -93,6 +93,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf while(resultSet.next()) { // todo - should refactor this for view etc to use too. + // todo - Add display values (String labels for possibleValues, formatted #'s, etc) QRecord record = new QRecord(); records.add(record); record.setTableName(table.getName()); @@ -104,10 +105,6 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf QFieldMetaData qFieldMetaData = fieldList.get(i - 1); Serializable value = getValue(qFieldMetaData, resultSet, i); values.put(qFieldMetaData.getName(), value); - if(qFieldMetaData.getName().equals(table.getPrimaryKeyField())) - { - record.setPrimaryKey(value); - } } } diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java new file mode 100644 index 00000000..c6e56fb6 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java @@ -0,0 +1,96 @@ +/* + * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + */ + +package com.kingsrook.qqq.backend.module.rdbms.actions; + + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest; +import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.data.QRecordWithStatus; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; +import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface; +import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInterface +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public UpdateResult execute(UpdateRequest updateRequest) throws QException + { + try + { + UpdateResult rs = new UpdateResult(); + QTableMetaData table = updateRequest.getTable(); + + List recordsWithStatus = new ArrayList<>(); + rs.setRecords(recordsWithStatus); + + // todo - sql batch for performance + // todo - if setting a bunch of records to have the same value, a single update where id IN? + int recordIndex = 0; + for(QRecord record : updateRequest.getRecords()) + { + List updateableFields = table.getFields().values().stream() + .filter(field -> !field.getName().equals("id")) // todo - intent here is to avoid non-updateable fields. + .filter(field -> record.getValues().containsKey(field.getName())) + .toList(); + + String columns = updateableFields.stream() + .map(f -> this.getColumnName(f) + " = ?") + .collect(Collectors.joining(", ")); + + String tableName = table.getName(); + StringBuilder sql = new StringBuilder("UPDATE ").append(tableName) + .append(" SET ").append(columns) + .append(" WHERE ").append(getColumnName(table.getField(table.getPrimaryKeyField()))).append(" = ?"); + + // todo sql customization - can edit sql and/or param list + + ConnectionManager connectionManager = new ConnectionManager(); + Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(updateRequest.getBackend())); + + QRecordWithStatus recordWithStatus = new QRecordWithStatus(record); + recordsWithStatus.add(recordWithStatus); + + try + { + List params = new ArrayList<>(); + for(QFieldMetaData field : updateableFields) + { + params.add(record.getValue(field.getName())); + } + params.add(record.getValue(table.getPrimaryKeyField())); + QueryManager.executeUpdate(connection, sql.toString(), params); + // todo - auto-updated values, e.g., modifyDate... maybe need to re-select? + } + catch(Exception e) + { + recordWithStatus.setErrors(new ArrayList<>(List.of(e))); + } + } + + return rs; + } + catch(Exception e) + { + throw new QException("Error executing update: " + e.getMessage(), e); + } + } + +} diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java new file mode 100644 index 00000000..2f9506b1 --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java @@ -0,0 +1,139 @@ +/* + * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + */ + +package com.kingsrook.qqq.backend.module.rdbms.actions; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest; +import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class RDBMSUpdateActionTest extends RDBMSActionTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + public void beforeEach() throws Exception + { + super.primeTestDatabase(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testUpdateOne() throws Exception + { + UpdateRequest updateRequest = initUpdateRequest(); + QRecord record = new QRecord().withTableName("person") + .withValue("id", 2) + .withValue("firstName", "James") + .withValue("lastName", "Kirk") + .withValue("email", "jamestk@starfleet.net") + .withValue("birthDate", "2210-05-20"); + updateRequest.setRecords(List.of(record)); + UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest); + assertEquals(1, updateResult.getRecords().size(), "Should return 1 row"); + assertEquals(2, updateResult.getRecords().get(0).getValue("id"), "Should have id=2 in the row"); + assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + runTestSql("SELECT * FROM person WHERE last_name = 'Kirk'", (rs -> { + int rowsFound = 0; + while(rs.next()) + { + rowsFound++; + assertEquals(2, rs.getInt("id")); + assertEquals("James", rs.getString("first_name")); + assertEquals("2210-05-20", rs.getString("birth_date")); + } + assertEquals(1, rowsFound); + })); + runTestSql("SELECT * FROM person WHERE last_name = 'Maes'", (rs -> { + if(rs.next()) + { + fail("Should not have found Maes any more."); + } + })); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testUpdateMany() throws Exception + { + UpdateRequest updateRequest = initUpdateRequest(); + QRecord record1 = new QRecord().withTableName("person") + .withValue("id", 1) + .withValue("firstName", "Darren") + .withValue("lastName", "From Bewitched") + .withValue("birthDate", "1900-01-01"); + + QRecord record2 = new QRecord().withTableName("person") + .withValue("id", 3) + .withValue("firstName", "Wilt") + .withValue("birthDate", null); + + updateRequest.setRecords(List.of(record1, record2)); + UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest); + assertEquals(2, updateResult.getRecords().size(), "Should return 2 rows"); + assertEquals(1, updateResult.getRecords().get(0).getValue("id"), "Should have expected ids in the row"); + assertEquals(3, updateResult.getRecords().get(1).getValue("id"), "Should have expected ids in the row"); + assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + runTestSql("SELECT * FROM person WHERE last_name = 'From Bewitched'", (rs -> { + int rowsFound = 0; + while(rs.next()) + { + rowsFound++; + assertEquals(1, rs.getInt("id")); + assertEquals("Darren", rs.getString("first_name")); + assertEquals("From Bewitched", rs.getString("last_name")); + assertEquals("1900-01-01", rs.getString("birth_date")); + } + assertEquals(1, rowsFound); + })); + runTestSql("SELECT * FROM person WHERE last_name = 'Chamberlain'", (rs -> { + int rowsFound = 0; + while(rs.next()) + { + rowsFound++; + assertEquals(3, rs.getInt("id")); + assertEquals("Wilt", rs.getString("first_name")); + assertNull(rs.getString("birth_date")); + } + assertEquals(1, rowsFound); + })); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private UpdateRequest initUpdateRequest() + { + UpdateRequest updateRequest = new UpdateRequest(); + updateRequest.setInstance(defineInstance()); + updateRequest.setTableName(defineTablePerson().getName()); + return updateRequest; + } + +} \ No newline at end of file