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 8a532325..3b730336 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 @@ -52,6 +52,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; @@ -60,6 +61,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey; 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; @@ -183,7 +185,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 17663aa7..a02d223e 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 @@ -54,6 +54,7 @@ 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.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; @@ -296,7 +297,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); } @@ -307,6 +309,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 ae44f8fe..94067332 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 @@ -48,6 +48,7 @@ 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; import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; /******************************************************************************* @@ -113,9 +114,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)) { @@ -153,7 +155,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 34832bdb..3ea783a6 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 @@ -892,6 +892,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)); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -1252,6 +1288,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;