From 74d003ed3c18910912f53c52611ddf182ca1c43a Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 22 May 2023 16:05:34 -0500 Subject: [PATCH] api association fixes; mostly about propagating ids/fkeys, and having fields (in maps) as expected field types --- .../core/actions/tables/InsertAction.java | 5 ++- .../core/actions/tables/QueryAction.java | 9 +++-- .../core/actions/tables/UpdateAction.java | 14 ++++++- .../ValidateRecordSecurityLockHelper.java | 11 ++++-- .../qqq/api/actions/QRecordApiAdapter.java | 5 ++- .../api/javalin/QJavalinApiHandlerTest.java | 37 +++++++++++++++++++ 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java index 95b8cdbe..4a47a7ed 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java @@ -53,6 +53,7 @@ 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.data.QRecord; 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.joins.JoinOn; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; @@ -63,6 +64,7 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; @@ -237,7 +239,8 @@ public class InsertAction extends AbstractQActionFunction queryOutputRecords) throws QException { - LOG.info("In manageAssociations for " + queryInput.getTableName() + " with " + queryOutputRecords.size() + " records"); QTableMetaData table = queryInput.getTable(); for(Association association : CollectionUtils.nonNullList(table.getAssociations())) { @@ -149,9 +149,10 @@ public class QueryAction Set values = new HashSet<>(); for(QRecord record : queryOutputRecords) { - Serializable value = record.getValue(joinOn.getLeftField()); - values.add(value); - outerResultMap.add(List.of(value), record); + Serializable value = record.getValue(joinOn.getLeftField()); + Serializable valueAsType = ValueUtils.getValueAsFieldType(table.getField(joinOn.getLeftField()).getType(), value); + values.add(valueAsType); + outerResultMap.add(List.of(valueAsType), record); } filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.IN, new ArrayList<>(values))); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java index aa3abccc..23a91d67 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java @@ -58,6 +58,7 @@ 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.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; @@ -400,7 +401,8 @@ public class UpdateAction ////////////////////////////////////////////////////////////////////////////////////////////////////////// for(JoinOn joinOn : join.getJoinOns()) { - associatedRecord.setValue(joinOn.getRightField(), record.getValue(joinOn.getLeftField())); + QFieldType type = table.getField(joinOn.getLeftField()).getType(); + associatedRecord.setValue(joinOn.getRightField(), ValueUtils.getValueAsFieldType(type, record.getValue(joinOn.getLeftField()))); } nextLevelInserts.add(associatedRecord); } @@ -411,6 +413,16 @@ public class UpdateAction /////////////////////////////////////////////////////////////////////////////// idsBeingUpdated.add(associatedId); nextLevelUpdates.add(associatedRecord); + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // make sure the child record being updated has its join fields populated (same as an insert). // + // this will make the next update action much happier // + ///////////////////////////////////////////////////////////////////////////////////////////////// + for(JoinOn joinOn : join.getJoinOns()) + { + QFieldType type = table.getField(joinOn.getLeftField()).getType(); + associatedRecord.setValue(joinOn.getRightField(), ValueUtils.getValueAsFieldType(type, record.getValue(joinOn.getLeftField()))); + } } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java index bcc18c3e..041ed54a 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java @@ -49,6 +49,7 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessa import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.ListingHash; import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; /******************************************************************************* @@ -114,9 +115,10 @@ public class ValidateRecordSecurityLockHelper //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // else look for the joined record - if it isn't found, assume a fail - else validate security value if found // //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - QJoinMetaData leftMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(0)); - QJoinMetaData rightMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(recordSecurityLock.getJoinNameChain().size() - 1)); - QTableMetaData leftMostJoinTable = QContext.getQInstance().getTable(leftMostJoin.getLeftTable()); + QJoinMetaData leftMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(0)); + QJoinMetaData rightMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(recordSecurityLock.getJoinNameChain().size() - 1)); + QTableMetaData rightMostJoinTable = QContext.getQInstance().getTable(rightMostJoin.getRightTable()); + QTableMetaData leftMostJoinTable = QContext.getQInstance().getTable(leftMostJoin.getLeftTable()); for(List inputRecordPage : CollectionUtils.getPages(records, 500)) { @@ -154,7 +156,8 @@ public class ValidateRecordSecurityLockHelper for(JoinOn joinOn : rightMostJoin.getJoinOns()) { - Serializable inputRecordValue = inputRecord.getValue(joinOn.getRightField()); + QFieldType type = rightMostJoinTable.getField(joinOn.getRightField()).getType(); + Serializable inputRecordValue = ValueUtils.getValueAsFieldType(type, inputRecord.getValue(joinOn.getRightField())); inputRecordJoinValues.add(inputRecordValue); subFilter.addCriteria(inputRecordValue == null diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/QRecordApiAdapter.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/QRecordApiAdapter.java index 412a0ad5..687fe2aa 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/QRecordApiAdapter.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/QRecordApiAdapter.java @@ -183,7 +183,10 @@ public class QRecordApiAdapter { if(subObject instanceof JSONObject subJsonObject) { - QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiName, apiVersion, includePrimaryKey); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // make sure to always include primary keys (boolean param) on calls for children - to help determine insert/update cases // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + QRecord subRecord = apiJsonObjectToQRecord(subJsonObject, association.getAssociatedTableName(), apiName, apiVersion, true); qRecord.withAssociatedRecord(association.getName(), subRecord); } else diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java index a358ff3e..1011cbb1 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java @@ -938,6 +938,42 @@ class QJavalinApiHandlerTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testUpdateAssociations() throws QException + { + insert1Order3Lines4LineExtrinsicsAnd1OrderExtrinsic(); + + HttpResponse response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/order/1") + .body(""" + {"orderLines": + [ + {"id": 1, "lineNumber": 1, "sku": "BASIC1", "quantity": 47}, + {"id": 2}, + {"id": 3} + ] + } + """) + .asString(); + assertErrorResponse(HttpStatus.NO_CONTENT_204, null, response); + + QRecord orderRecord = getRecord(TestUtils.TABLE_NAME_ORDER, 1); + List orderLines = orderRecord.getAssociatedRecords().get("orderLines"); + assertThat(orderLines) + .withFailMessage("order lines should be found on order").isNotNull().isNotEmpty() + .withFailMessage("Should have a line with id 1").filteredOn(r -> r.getValueInteger("id").equals(1)).hasSize(1) + .withFailMessage("line with id 1 should have quantity updated to 47").first().matches(r -> r.getValue("quantity").equals(47)); + + assertThat(orderLines) + .withFailMessage("order lines should be found on order").isNotNull().isNotEmpty() + .withFailMessage("Should have a line with id 2").filteredOn(r -> r.getValueInteger("id").equals(2)).hasSize(1) + .withFailMessage("line with id 2 should have original quantity (42)").first().matches(r -> r.getValue("quantity").equals(42)); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -1400,6 +1436,7 @@ class QJavalinApiHandlerTest extends BaseTest GetInput getInput = new GetInput(); getInput.setTableName(tableName); getInput.setPrimaryKey(id); + getInput.setIncludeAssociations(true); GetOutput getOutput = new GetAction().execute(getInput); QRecord record = getOutput.getRecord(); return record;