api association fixes; mostly about propagating ids/fkeys, and having fields (in maps) as expected field types (cherry pick 74d003ed)

This commit is contained in:
2023-05-22 16:05:34 -05:00
parent 9dc6f4ccf8
commit 7491e5f819
6 changed files with 70 additions and 11 deletions

View File

@ -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.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; 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.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.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; 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.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; 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; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -183,7 +185,8 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
{ {
for(JoinOn joinOn : join.getJoinOns()) 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); nextLevelInserts.add(associatedRecord);
} }

View File

@ -56,6 +56,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash; import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/******************************************************************************* /*******************************************************************************
@ -123,7 +124,6 @@ public class QueryAction
*******************************************************************************/ *******************************************************************************/
private void manageAssociations(QueryInput queryInput, List<QRecord> queryOutputRecords) throws QException private void manageAssociations(QueryInput queryInput, List<QRecord> queryOutputRecords) throws QException
{ {
LOG.info("In manageAssociations for " + queryInput.getTableName() + " with " + queryOutputRecords.size() + " records");
QTableMetaData table = queryInput.getTable(); QTableMetaData table = queryInput.getTable();
for(Association association : CollectionUtils.nonNullList(table.getAssociations())) for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{ {
@ -150,8 +150,9 @@ public class QueryAction
for(QRecord record : queryOutputRecords) for(QRecord record : queryOutputRecords)
{ {
Serializable value = record.getValue(joinOn.getLeftField()); Serializable value = record.getValue(joinOn.getLeftField());
values.add(value); Serializable valueAsType = ValueUtils.getValueAsFieldType(table.getField(joinOn.getLeftField()).getType(), value);
outerResultMap.add(List.of(value), record); values.add(valueAsType);
outerResultMap.add(List.of(valueAsType), record);
} }
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.IN, new ArrayList<>(values))); filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.IN, new ArrayList<>(values)));
} }

View File

@ -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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel; 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.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.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
@ -296,7 +297,8 @@ public class UpdateAction
////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////
for(JoinOn joinOn : join.getJoinOns()) 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); nextLevelInserts.add(associatedRecord);
} }
@ -307,6 +309,16 @@ public class UpdateAction
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
idsBeingUpdated.add(associatedId); idsBeingUpdated.add(associatedId);
nextLevelUpdates.add(associatedRecord); 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())));
}
} }
} }

View File

@ -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.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash; import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/******************************************************************************* /*******************************************************************************
@ -115,6 +116,7 @@ public class ValidateRecordSecurityLockHelper
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QJoinMetaData leftMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(0)); QJoinMetaData leftMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(0));
QJoinMetaData rightMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(recordSecurityLock.getJoinNameChain().size() - 1)); 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()); QTableMetaData leftMostJoinTable = QContext.getQInstance().getTable(leftMostJoin.getLeftTable());
for(List<QRecord> inputRecordPage : CollectionUtils.getPages(records, 500)) for(List<QRecord> inputRecordPage : CollectionUtils.getPages(records, 500))
@ -153,7 +155,8 @@ public class ValidateRecordSecurityLockHelper
for(JoinOn joinOn : rightMostJoin.getJoinOns()) 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); inputRecordJoinValues.add(inputRecordValue);
subFilter.addCriteria(inputRecordValue == null subFilter.addCriteria(inputRecordValue == null

View File

@ -183,7 +183,10 @@ public class QRecordApiAdapter
{ {
if(subObject instanceof JSONObject subJsonObject) 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); qRecord.withAssociatedRecord(association.getName(), subRecord);
} }
else else

View File

@ -892,6 +892,42 @@ class QJavalinApiHandlerTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testUpdateAssociations() throws QException
{
insert1Order3Lines4LineExtrinsicsAnd1OrderExtrinsic();
HttpResponse<String> 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<QRecord> 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 getInput = new GetInput();
getInput.setTableName(tableName); getInput.setTableName(tableName);
getInput.setPrimaryKey(id); getInput.setPrimaryKey(id);
getInput.setIncludeAssociations(true);
GetOutput getOutput = new GetAction().execute(getInput); GetOutput getOutput = new GetAction().execute(getInput);
QRecord record = getOutput.getRecord(); QRecord record = getOutput.getRecord();
return record; return record;