Merge branch 'dev' into feature/CTLE-434-oms-update-business-logic

This commit is contained in:
2023-05-16 19:59:15 -05:00
5 changed files with 141 additions and 46 deletions

View File

@ -129,29 +129,14 @@ public class GetAction
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
// if the record wasn't found, see if we should look in cache-source // // if the record wasn't found, see if we should look in cache-source //
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
QRecord recordFromSource = tryToGetFromCacheSource(getInput, getOutput); QRecord recordFromSource = tryToGetFromCacheSource(getInput);
if(recordFromSource != null) if(recordFromSource != null)
{ {
/////////////////////////////////////////////////////////////////////////////////////////////////
// good, we found a record from the source, make sure we should cache it, and if so, do it now //
/////////////////////////////////////////////////////////////////////////////////////////////////
QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource); QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource);
boolean shouldCacheRecord = true; boolean shouldCacheRecord = shouldCacheRecord(table, recordToCache);
////////////////////////////////////////////////////////////////////////////////
// see if there are any exclustions that need to be considered for this table //
////////////////////////////////////////////////////////////////////////////////
recordMatchExclusionLoop:
for(CacheUseCase useCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
{
for(QQueryFilter filter : CollectionUtils.nonNullList(useCase.getExcludeRecordsMatching()))
{
if(BackendQueryFilterUtils.doesRecordMatch(filter, recordToCache))
{
LOG.info("Not caching record because it matches a use case's filter exclusion", new LogPair("record", recordToCache), new LogPair("filter", filter));
shouldCacheRecord = false;
break recordMatchExclusionLoop;
}
}
}
if(shouldCacheRecord) if(shouldCacheRecord)
{ {
InsertInput insertInput = new InsertInput(); InsertInput insertInput = new InsertInput();
@ -184,12 +169,50 @@ public class GetAction
/*******************************************************************************
**
*******************************************************************************/
private boolean shouldCacheRecord(QTableMetaData table, QRecord recordToCache)
{
boolean shouldCacheRecord = true;
recordMatchExclusionLoop:
for(CacheUseCase useCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
{
for(QQueryFilter filter : CollectionUtils.nonNullList(useCase.getExcludeRecordsMatching()))
{
if(BackendQueryFilterUtils.doesRecordMatch(filter, recordToCache))
{
LOG.info("Not caching record because it matches a use case's filter exclusion", new LogPair("record", recordToCache), new LogPair("filter", filter));
shouldCacheRecord = false;
break recordMatchExclusionLoop;
}
}
}
return (shouldCacheRecord);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static QRecord mapSourceRecordToCacheRecord(QTableMetaData table, QRecord recordFromSource) private static QRecord mapSourceRecordToCacheRecord(QTableMetaData table, QRecord recordFromSource)
{ {
QRecord cacheRecord = new QRecord(recordFromSource); QRecord cacheRecord = new QRecord(recordFromSource);
//////////////////////////////////////////////////////////////////////////////////////////////
// make sure every value in the qRecord is set, because we will possibly be doing an update //
// on this record and want to null out any fields not set, not leave them populated //
//////////////////////////////////////////////////////////////////////////////////////////////
for(String fieldName : table.getFields().keySet())
{
if(!cacheRecord.getValues().containsKey(fieldName))
{
cacheRecord.setValue(fieldName, null);
}
}
if(StringUtils.hasContent(table.getCacheOf().getCachedDateFieldName())) if(StringUtils.hasContent(table.getCacheOf().getCachedDateFieldName()))
{ {
cacheRecord.setValue(table.getCacheOf().getCachedDateFieldName(), Instant.now()); cacheRecord.setValue(table.getCacheOf().getCachedDateFieldName(), Instant.now());
@ -212,33 +235,54 @@ public class GetAction
Instant cachedDate = cachedRecord.getValueInstant(table.getCacheOf().getCachedDateFieldName()); Instant cachedDate = cachedRecord.getValueInstant(table.getCacheOf().getCachedDateFieldName());
if(cachedDate == null || cachedDate.isBefore(Instant.now().minus(expirationSeconds, ChronoUnit.SECONDS))) if(cachedDate == null || cachedDate.isBefore(Instant.now().minus(expirationSeconds, ChronoUnit.SECONDS)))
{ {
QRecord recordFromSource = tryToGetFromCacheSource(getInput, getOutput); //////////////////////////////////////////////////////////////////////////
// keep the serial key from the old record in case we need to delete it //
//////////////////////////////////////////////////////////////////////////
Serializable oldRecordPrimaryKey = getOutput.getRecord().getValue(table.getPrimaryKeyField());
boolean shouldDeleteCachedRecord = true;
///////////////////////////////////////////
// fetch record from original source now //
///////////////////////////////////////////
QRecord recordFromSource = tryToGetFromCacheSource(getInput);
if(recordFromSource != null) if(recordFromSource != null)
{ {
/////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// if the record was found in the source, update it in the cache // // if the record was found in the source, put it into the output //
/////////////////////////////////////////////////////////////////// // object so returned back to caller, check that it should actually //
// be cached before doing so //
//////////////////////////////////////////////////////////////////////
QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource); QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource);
recordToCache.setValue(table.getPrimaryKeyField(), cachedRecord.getValue(table.getPrimaryKeyField())); recordToCache.setValue(table.getPrimaryKeyField(), cachedRecord.getValue(table.getPrimaryKeyField()));
getOutput.setRecord(recordToCache);
if(shouldCacheRecord(table, recordToCache))
{
UpdateInput updateInput = new UpdateInput(); UpdateInput updateInput = new UpdateInput();
updateInput.setTableName(getInput.getTableName()); updateInput.setTableName(getInput.getTableName());
updateInput.setRecords(List.of(recordToCache)); updateInput.setRecords(List.of(recordToCache));
UpdateOutput updateOutput = new UpdateAction().execute(updateInput); UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
getOutput.setRecord(updateOutput.getRecords().get(0)); getOutput.setRecord(updateOutput.getRecords().get(0));
shouldDeleteCachedRecord = false;
}
} }
else else
{
///////////////////////////////////////////////////////////////////////////////////////
// if we did not get a record back from the source, empty out the getOutput's record //
///////////////////////////////////////////////////////////////////////////////////////
getOutput.setRecord(null);
}
if(shouldDeleteCachedRecord)
{ {
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// if the record is no longer in the source, then remove it from the cache // // if the record is no longer in the source, then remove it from the cache //
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
DeleteInput deleteInput = new DeleteInput(); DeleteInput deleteInput = new DeleteInput();
deleteInput.setTableName(getInput.getTableName()); deleteInput.setTableName(getInput.getTableName());
deleteInput.setPrimaryKeys(List.of(getOutput.getRecord().getValue(table.getPrimaryKeyField()))); deleteInput.setPrimaryKeys(List.of(oldRecordPrimaryKey));
new DeleteAction().execute(deleteInput); new DeleteAction().execute(deleteInput);
getOutput.setRecord(null);
} }
} }
} }
@ -249,7 +293,7 @@ public class GetAction
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private QRecord tryToGetFromCacheSource(GetInput getInput, GetOutput getOutput) throws QException private QRecord tryToGetFromCacheSource(GetInput getInput) throws QException
{ {
QRecord recordFromSource = null; QRecord recordFromSource = null;
QTableMetaData table = getInput.getTable(); QTableMetaData table = getInput.getTable();

View File

@ -111,13 +111,13 @@ public class JoinsContext
if(join.getLeftTable().equals(tmpTable.getName())) if(join.getLeftTable().equals(tmpTable.getName()))
{ {
QueryJoin queryJoin = new ImplicitQueryJoinForSecurityLock().withJoinMetaData(join).withType(QueryJoin.Type.INNER); QueryJoin queryJoin = new ImplicitQueryJoinForSecurityLock().withJoinMetaData(join).withType(QueryJoin.Type.INNER);
this.queryJoins.add(queryJoin); // todo something else with aliases? probably. this.addQueryJoin(queryJoin);
tmpTable = instance.getTable(join.getRightTable()); tmpTable = instance.getTable(join.getRightTable());
} }
else if(join.getRightTable().equals(tmpTable.getName())) else if(join.getRightTable().equals(tmpTable.getName()))
{ {
QueryJoin queryJoin = new ImplicitQueryJoinForSecurityLock().withJoinMetaData(join.flip()).withType(QueryJoin.Type.INNER); QueryJoin queryJoin = new ImplicitQueryJoinForSecurityLock().withJoinMetaData(join.flip()).withType(QueryJoin.Type.INNER);
this.queryJoins.add(queryJoin); // todo something else with aliases? probably. this.addQueryJoin(queryJoin); //
tmpTable = instance.getTable(join.getLeftTable()); tmpTable = instance.getTable(join.getLeftTable());
} }
else else
@ -145,6 +145,20 @@ public class JoinsContext
/*******************************************************************************
** Add a query join to the list of query joins, and "process it"
**
** use this method to add to the list, instead of ever adding directly, as it's
** important do to that process step (and we've had bugs when it wasn't done).
*******************************************************************************/
private void addQueryJoin(QueryJoin queryJoin) throws QException
{
this.queryJoins.add(queryJoin);
processQueryJoin(queryJoin);
}
/******************************************************************************* /*******************************************************************************
** If there are any joins in the context that don't have a join meta data, see ** If there are any joins in the context that don't have a join meta data, see
** if we can find the JoinMetaData to use for them by looking at the main table's ** if we can find the JoinMetaData to use for them by looking at the main table's
@ -236,8 +250,7 @@ public class JoinsContext
QueryJoin queryJoinToAdd = makeQueryJoinFromJoinAndTableNames(nextTable, tmpTable, joinToAdd); QueryJoin queryJoinToAdd = makeQueryJoinFromJoinAndTableNames(nextTable, tmpTable, joinToAdd);
queryJoinToAdd.setType(queryJoin.getType()); queryJoinToAdd.setType(queryJoin.getType());
addedAnyQueryJoins = true; addedAnyQueryJoins = true;
this.queryJoins.add(queryJoinToAdd); // todo something else with aliases? probably. this.addQueryJoin(queryJoinToAdd);
processQueryJoin(queryJoinToAdd);
} }
} }
@ -410,8 +423,7 @@ public class JoinsContext
QueryJoin queryJoin = makeQueryJoinFromJoinAndTableNames(mainTableName, filterTable, join); QueryJoin queryJoin = makeQueryJoinFromJoinAndTableNames(mainTableName, filterTable, join);
if(queryJoin != null) if(queryJoin != null)
{ {
this.queryJoins.add(queryJoin); // todo something else with aliases? probably. this.addQueryJoin(queryJoin);
processQueryJoin(queryJoin);
found = true; found = true;
break; break;
} }
@ -420,8 +432,7 @@ public class JoinsContext
if(!found) if(!found)
{ {
QueryJoin queryJoin = new QueryJoin().withJoinTable(filterTable).withType(QueryJoin.Type.INNER); QueryJoin queryJoin = new QueryJoin().withJoinTable(filterTable).withType(QueryJoin.Type.INNER);
this.queryJoins.add(queryJoin); // todo something else with aliases? probably. this.addQueryJoin(queryJoin);
processQueryJoin(queryJoin);
} }
} }
} }

View File

@ -131,10 +131,10 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
} }
} }
try
{
Long mark = System.currentTimeMillis(); Long mark = System.currentTimeMillis();
try
{
////////////////////////////////////////////// //////////////////////////////////////////////
// execute the query - iterate over results // // execute the query - iterate over results //
////////////////////////////////////////////// //////////////////////////////////////////////
@ -173,6 +173,11 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
return queryOutput; return queryOutput;
} }
catch(Exception e)
{
logSQL(sql, params, mark);
throw (e);
}
finally finally
{ {
if(needToCloseConnection) if(needToCloseConnection)

View File

@ -1416,6 +1416,39 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRecordSecurityFromJoinTableAlsoImplicitlyInQuery() throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER_LINE);
///////////////////////////////////////////////////////////////////////////////////////////
// orders 1, 2, and 3 are from store 1, so their lines (5 in total) should be found. //
// note, order 2 has the line with mis-matched store id - but, that shouldn't apply here //
///////////////////////////////////////////////////////////////////////////////////////////
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("order.id", QCriteriaOperator.IN, List.of(1, 2, 3, 4))));
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1));
assertThat(new QueryAction().execute(queryInput).getRecords()).hasSize(5);
///////////////////////////////////////////////////////////////////
// order 4 should be the only one found this time (with 2 lines) //
///////////////////////////////////////////////////////////////////
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("order.id", QCriteriaOperator.IN, List.of(1, 2, 3, 4))));
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 2));
assertThat(new QueryAction().execute(queryInput).getRecords()).hasSize(2);
////////////////////////////////////////////////////////////////
// make sure we're also good if we explicitly join this table //
////////////////////////////////////////////////////////////////
queryInput.withQueryJoin(new QueryJoin().withJoinTable(TestUtils.TABLE_NAME_ORDER).withSelect(true));
assertThat(new QueryAction().execute(queryInput).getRecords()).hasSize(2);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -178,10 +178,12 @@ if(writeTableMetaData)
.withRecordLabelFields("TODO") .withRecordLabelFields("TODO")
.withBackendName(TODO) .withBackendName(TODO)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withUniqueKey(TODO)
.withRecordSecurityLock(TODO)
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.TODO))
.withFieldsFromEntity({className}.class) .withFieldsFromEntity({className}.class)
.withBackendDetails(new RDBMSTableBackendDetails() .withBackendDetails(new RDBMSTableBackendDetails()
.withTableName("{tableName}") .withTableName("{tableName}"))
)
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id"))) .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id")))
.withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of({dataFieldNames}))) .withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of({dataFieldNames})))
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate"))); .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));