mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Better testing on join reports, possible value translations; renamed left & right in QueryJoin (now joinTable, baseTable)
This commit is contained in:
@ -27,9 +27,11 @@ import java.math.BigDecimal;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -119,6 +121,10 @@ public class GenerateReportAction
|
|||||||
{
|
{
|
||||||
report = reportInput.getInstance().getReport(reportInput.getReportName());
|
report = reportInput.getInstance().getReport(reportInput.getReportName());
|
||||||
reportFormat = reportInput.getReportFormat();
|
reportFormat = reportInput.getReportFormat();
|
||||||
|
if(reportFormat == null)
|
||||||
|
{
|
||||||
|
throw new QException("Report format was not specified.");
|
||||||
|
}
|
||||||
reportStreamer = reportFormat.newReportStreamer();
|
reportStreamer = reportFormat.newReportStreamer();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -300,7 +306,9 @@ public class GenerateReportAction
|
|||||||
queryInput.setTableName(dataSource.getSourceTable());
|
queryInput.setTableName(dataSource.getSourceTable());
|
||||||
queryInput.setFilter(queryFilter);
|
queryInput.setFilter(queryFilter);
|
||||||
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||||
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
|
||||||
|
queryInput.setShouldTranslatePossibleValues(true);
|
||||||
|
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins())));
|
||||||
|
|
||||||
if(dataSource.getQueryInputCustomizer() != null)
|
if(dataSource.getQueryInputCustomizer() != null)
|
||||||
{
|
{
|
||||||
@ -355,6 +363,45 @@ public class GenerateReportAction
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private Set<String> setupFieldsToTranslatePossibleValues(ReportInput reportInput, QReportDataSource dataSource, JoinsContext joinsContext)
|
||||||
|
{
|
||||||
|
Set<String> fieldsToTranslatePossibleValues = new HashSet<>();
|
||||||
|
|
||||||
|
for(QReportView view : report.getViews())
|
||||||
|
{
|
||||||
|
for(QReportField column : CollectionUtils.nonNullList(view.getColumns()))
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if this is a column marked as ShowPossibleValueLabel, then we need to translate it //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(column.getShowPossibleValueLabel())
|
||||||
|
{
|
||||||
|
String effectiveFieldName = Objects.requireNonNullElse(column.getSourceFieldName(), column.getName());
|
||||||
|
fieldsToTranslatePossibleValues.add(effectiveFieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String summaryField : CollectionUtils.nonNullList(view.getPivotFields()))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// all pivotFields that are possible value sources are implicitly translated //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||||
|
if(table.getField(summaryField).getPossibleValueSourceName() != null)
|
||||||
|
{
|
||||||
|
fieldsToTranslatePossibleValues.add(summaryField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (fieldsToTranslatePossibleValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -375,8 +422,7 @@ public class GenerateReportAction
|
|||||||
|
|
||||||
for(Serializable value : criterion.getValues())
|
for(Serializable value : criterion.getValues())
|
||||||
{
|
{
|
||||||
String valueAsString = ValueUtils.getValueAsString(value);
|
String valueAsString = ValueUtils.getValueAsString(value);
|
||||||
// Serializable interpretedValue = variableInterpreter.interpret(valueAsString);
|
|
||||||
Serializable interpretedValue = variableInterpreter.interpretForObject(valueAsString);
|
Serializable interpretedValue = variableInterpreter.interpretForObject(valueAsString);
|
||||||
newValues.add(interpretedValue);
|
newValues.add(interpretedValue);
|
||||||
}
|
}
|
||||||
@ -476,6 +522,9 @@ public class GenerateReportAction
|
|||||||
Serializable summaryValue = record.getValue(summaryField);
|
Serializable summaryValue = record.getValue(summaryField);
|
||||||
if(table.getField(summaryField).getPossibleValueSourceName() != null)
|
if(table.getField(summaryField).getPossibleValueSourceName() != null)
|
||||||
{
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// so, this is kinda a thing - where we implicitly use possible-value labels (e.g., display values) for pivot fields... //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
summaryValue = record.getDisplayValue(summaryField);
|
summaryValue = record.getDisplayValue(summaryField);
|
||||||
}
|
}
|
||||||
key.add(summaryField, summaryValue);
|
key.add(summaryField, summaryValue);
|
||||||
|
@ -110,7 +110,7 @@ public class QueryAction
|
|||||||
{
|
{
|
||||||
qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
||||||
}
|
}
|
||||||
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records, queryInput.getQueryJoins());
|
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records, queryInput.getQueryJoins(), queryInput.getFieldsToTranslatePossibleValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queryInput.getShouldGenerateDisplayValues())
|
if(queryInput.getShouldGenerateDisplayValues())
|
||||||
|
@ -96,7 +96,7 @@ public class QPossibleValueTranslator
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records)
|
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||||
{
|
{
|
||||||
translatePossibleValuesInRecords(table, records, Collections.emptyList());
|
translatePossibleValuesInRecords(table, records, Collections.emptyList(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -104,15 +104,21 @@ public class QPossibleValueTranslator
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** For a list of records, translate their possible values (populating their display values)
|
** For a list of records, translate their possible values (populating their display values)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records, List<QueryJoin> queryJoins)
|
public void translatePossibleValuesInRecords(QTableMetaData table, List<QRecord> records, List<QueryJoin> queryJoins, Set<String> limitedToFieldNames)
|
||||||
{
|
{
|
||||||
if(records == null || table == null)
|
if(records == null || table == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(limitedToFieldNames != null && limitedToFieldNames.isEmpty())
|
||||||
|
{
|
||||||
|
LOG.debug("We were asked to translate possible values, but then given an empty set of fields to translate, so noop.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LOG.debug("Translating possible values in [" + records.size() + "] records from the [" + table.getName() + "] table.");
|
LOG.debug("Translating possible values in [" + records.size() + "] records from the [" + table.getName() + "] table.");
|
||||||
primePvsCache(table, records, queryJoins);
|
primePvsCache(table, records, queryJoins, limitedToFieldNames);
|
||||||
|
|
||||||
for(QRecord record : records)
|
for(QRecord record : records)
|
||||||
{
|
{
|
||||||
@ -120,7 +126,10 @@ public class QPossibleValueTranslator
|
|||||||
{
|
{
|
||||||
if(field.getPossibleValueSourceName() != null)
|
if(field.getPossibleValueSourceName() != null)
|
||||||
{
|
{
|
||||||
record.setDisplayValue(field.getName(), translatePossibleValue(field, record.getValue(field.getName())));
|
if(limitedToFieldNames == null || limitedToFieldNames.contains(field.getName()))
|
||||||
|
{
|
||||||
|
record.setDisplayValue(field.getName(), translatePossibleValue(field, record.getValue(field.getName())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,25 +139,25 @@ public class QPossibleValueTranslator
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////
|
QTableMetaData joinTable = qInstance.getTable(queryJoin.getJoinTable());
|
||||||
// todo - aliases aren't be handled right //
|
|
||||||
////////////////////////////////////////////
|
|
||||||
QTableMetaData joinTable = qInstance.getTable(queryJoin.getRightTable());
|
|
||||||
for(QFieldMetaData field : joinTable.getFields().values())
|
for(QFieldMetaData field : joinTable.getFields().values())
|
||||||
{
|
{
|
||||||
|
String joinFieldName = Objects.requireNonNullElse(queryJoin.getAlias(), joinTable.getName()) + "." + field.getName();
|
||||||
if(field.getPossibleValueSourceName() != null)
|
if(field.getPossibleValueSourceName() != null)
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////
|
if(limitedToFieldNames == null || limitedToFieldNames.contains(joinFieldName))
|
||||||
// avoid circling-back upon the source table //
|
|
||||||
///////////////////////////////////////////////
|
|
||||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
|
||||||
if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()) && table.getName().equals(possibleValueSource.getTableName()))
|
|
||||||
{
|
{
|
||||||
continue;
|
///////////////////////////////////////////////
|
||||||
}
|
// avoid circling-back upon the source table //
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||||
|
if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()) && table.getName().equals(possibleValueSource.getTableName()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String joinFieldName = joinTable.getName() + "." + field.getName();
|
record.setDisplayValue(joinFieldName, translatePossibleValue(field, record.getValue(joinFieldName)));
|
||||||
record.setDisplayValue(joinFieldName, translatePossibleValue(field, record.getValue(joinFieldName)));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,11 +388,13 @@ public class QPossibleValueTranslator
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
|
** prime the cache (e.g., by doing bulk-queries) for table-based PVS's
|
||||||
** @param table the table that the records are from
|
*
|
||||||
|
* @param table the table that the records are from
|
||||||
** @param records the records that have the possible value id's (e.g., foreign keys)
|
** @param records the records that have the possible value id's (e.g., foreign keys)
|
||||||
* @param queryJoins
|
* @param queryJoins joins that were used as part of the query that led to the records.
|
||||||
|
* @param limitedToFieldNames set of names that are the only fields that get translated (null means all fields).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
void primePvsCache(QTableMetaData table, List<QRecord> records, List<QueryJoin> queryJoins)
|
void primePvsCache(QTableMetaData table, List<QRecord> records, List<QueryJoin> queryJoins, Set<String> limitedToFieldNames)
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// this is a map of String(tableName - the PVS table) to Pair(String (either "" for main table in a query, or join-table + "."), field (from the table being selected from)) //
|
// this is a map of String(tableName - the PVS table) to Pair(String (either "" for main table in a query, or join-table + "."), field (from the table being selected from)) //
|
||||||
@ -395,13 +406,13 @@ public class QPossibleValueTranslator
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
ListingHash<String, QPossibleValueSource> pvsesByTable = new ListingHash<>();
|
ListingHash<String, QPossibleValueSource> pvsesByTable = new ListingHash<>();
|
||||||
|
|
||||||
primePvsCacheTableListingHashLoader(table, fieldsByPvsTable, pvsesByTable, "");
|
primePvsCacheTableListingHashLoader(table, fieldsByPvsTable, pvsesByTable, "", table.getName(), limitedToFieldNames);
|
||||||
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||||
{
|
{
|
||||||
if(queryJoin.getSelect())
|
if(queryJoin.getSelect())
|
||||||
{
|
{
|
||||||
// todo - aliases probably not handled right
|
String aliasOrTableName = Objects.requireNonNullElse(queryJoin.getAlias(), queryJoin.getJoinTable());
|
||||||
primePvsCacheTableListingHashLoader(qInstance.getTable(queryJoin.getRightTable()), fieldsByPvsTable, pvsesByTable, queryJoin.getRightTable() + ".");
|
primePvsCacheTableListingHashLoader(qInstance.getTable(queryJoin.getJoinTable()), fieldsByPvsTable, pvsesByTable, aliasOrTableName + ".", queryJoin.getJoinTable(), limitedToFieldNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,8 +437,8 @@ public class QPossibleValueTranslator
|
|||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
// check if value is already cached //
|
// check if value is already cached //
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
QPossibleValueSource possibleValueSource = pvsesByTable.get(tableName).get(0);
|
QPossibleValueSource possibleValueSource = pvsesByTable.get(tableName).get(0);
|
||||||
Map<Serializable, String> cacheForPvs = possibleValueCache.computeIfAbsent(possibleValueSource.getName(), x -> new HashMap<>());
|
Map<Serializable, String> cacheForPvs = possibleValueCache.computeIfAbsent(possibleValueSource.getName(), x -> new HashMap<>());
|
||||||
|
|
||||||
if(!cacheForPvs.containsKey(fieldValue))
|
if(!cacheForPvs.containsKey(fieldValue))
|
||||||
{
|
{
|
||||||
@ -448,13 +459,19 @@ public class QPossibleValueTranslator
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Helper for the primePvsCache method
|
** Helper for the primePvsCache method
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void primePvsCacheTableListingHashLoader(QTableMetaData table, ListingHash<String, Pair<String, QFieldMetaData>> fieldsByPvsTable, ListingHash<String, QPossibleValueSource> pvsesByTable, String fieldNamePrefix)
|
private void primePvsCacheTableListingHashLoader(QTableMetaData table, ListingHash<String, Pair<String, QFieldMetaData>> fieldsByPvsTable, ListingHash<String, QPossibleValueSource> pvsesByTable, String fieldNamePrefix, String tableName, Set<String> limitedToFieldNames)
|
||||||
{
|
{
|
||||||
for(QFieldMetaData field : table.getFields().values())
|
for(QFieldMetaData field : table.getFields().values())
|
||||||
{
|
{
|
||||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||||
if(possibleValueSource != null && possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
if(possibleValueSource != null && possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||||
{
|
{
|
||||||
|
if(limitedToFieldNames != null && !limitedToFieldNames.contains(fieldNamePrefix + field.getName()))
|
||||||
|
{
|
||||||
|
LOG.debug("Skipping cache priming for translation of possible value field [" + fieldNamePrefix + field.getName() + "] - it's not in the limitedToFieldNames set.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
fieldsByPvsTable.add(possibleValueSource.getTableName(), Pair.of(fieldNamePrefix, field));
|
fieldsByPvsTable.add(possibleValueSource.getTableName(), Pair.of(fieldNamePrefix, field));
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -491,14 +508,38 @@ public class QPossibleValueTranslator
|
|||||||
queryInput.setTableName(tableName);
|
queryInput.setTableName(tableName);
|
||||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, page)));
|
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, page)));
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// this is needed to get record labels, which are what we use here... unclear if best! //
|
// when querying for possible values, we do want to generate their display values, which makes record labels, which are usually used as PVS labels //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(notTooDeep())
|
queryInput.setShouldGenerateDisplayValues(true);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// also, if this table uses any possible value fields as part of its own record label, then THOSE possible values need translated. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Set<String> possibleValueFieldsToTranslate = new HashSet<>();
|
||||||
|
for(QPossibleValueSource possibleValueSource : possibleValueSources)
|
||||||
{
|
{
|
||||||
// todo not commit...
|
if(possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||||
// queryInput.setShouldTranslatePossibleValues(true);
|
{
|
||||||
queryInput.setShouldGenerateDisplayValues(true);
|
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
|
||||||
|
for(String recordLabelField : CollectionUtils.nonNullList(table.getRecordLabelFields()))
|
||||||
|
{
|
||||||
|
QFieldMetaData field = table.getField(recordLabelField);
|
||||||
|
if(field.getPossibleValueSourceName() != null)
|
||||||
|
{
|
||||||
|
possibleValueFieldsToTranslate.add(field.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// an earlier version of this code got into stack overflows, so do a "cheap" check for recursion depth too... //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(!possibleValueFieldsToTranslate.isEmpty() && notTooDeep())
|
||||||
|
{
|
||||||
|
queryInput.setShouldTranslatePossibleValues(true);
|
||||||
|
queryInput.setFieldsToTranslatePossibleValues(possibleValueFieldsToTranslate);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debug("Priming PVS cache for [" + page.size() + "] ids from [" + tableName + "] table.");
|
LOG.debug("Priming PVS cache for [" + page.size() + "] ids from [" + tableName + "] table.");
|
||||||
|
@ -1107,7 +1107,7 @@ public class QInstanceValidator
|
|||||||
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
|
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
|
||||||
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||||
{
|
{
|
||||||
QTableMetaData joinTable = qInstance.getTable(queryJoin.getRightTable());
|
QTableMetaData joinTable = qInstance.getTable(queryJoin.getJoinTable());
|
||||||
if(joinTable != null)
|
if(joinTable != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -38,9 +38,13 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class JoinsContext
|
public class JoinsContext
|
||||||
{
|
{
|
||||||
private final QInstance instance;
|
private final QInstance instance;
|
||||||
private final String mainTableName;
|
private final String mainTableName;
|
||||||
private final List<QueryJoin> queryJoins;
|
private final List<QueryJoin> queryJoins;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
// note - will have entries for all tables, not just aliases. //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
private final Map<String, String> aliasToTableNameMap = new HashMap<>();
|
private final Map<String, String> aliasToTableNameMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
@ -57,8 +61,8 @@ public class JoinsContext
|
|||||||
|
|
||||||
for(QueryJoin queryJoin : this.queryJoins)
|
for(QueryJoin queryJoin : this.queryJoins)
|
||||||
{
|
{
|
||||||
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
|
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
|
||||||
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
if(aliasToTableNameMap.containsKey(tableNameOrAlias))
|
if(aliasToTableNameMap.containsKey(tableNameOrAlias))
|
||||||
{
|
{
|
||||||
throw (new QException("Duplicate table name or alias: " + tableNameOrAlias));
|
throw (new QException("Duplicate table name or alias: " + tableNameOrAlias));
|
||||||
@ -81,7 +85,8 @@ public class JoinsContext
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** For a given name (whether that's a table name or an alias in the query),
|
||||||
|
** get the actual table name (e.g., that could be passed to qInstance.getTable())
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public String resolveTableNameOrAliasToTableName(String nameOrAlias)
|
public String resolveTableNameOrAliasToTableName(String nameOrAlias)
|
||||||
{
|
{
|
||||||
@ -95,7 +100,8 @@ public class JoinsContext
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** For a given fieldName, which we expect may start with a tableNameOrAlias + '.',
|
||||||
|
** find the QFieldMetaData and the tableNameOrAlias that it corresponds to.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public FieldAndTableNameOrAlias getFieldAndTableNameOrAlias(String fieldName)
|
public FieldAndTableNameOrAlias getFieldAndTableNameOrAlias(String fieldName)
|
||||||
{
|
{
|
||||||
@ -124,6 +130,40 @@ public class JoinsContext
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Check if the given table name exists in the query - but that name may NOT
|
||||||
|
** be an alias - it must be an actual table name.
|
||||||
|
**
|
||||||
|
** e.g., Given:
|
||||||
|
** FROM `order` INNER JOIN line_item li
|
||||||
|
** hasAliasOrTable("order") => true
|
||||||
|
** hasAliasOrTable("li") => false
|
||||||
|
** hasAliasOrTable("line_item") => true
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean hasTable(String table)
|
||||||
|
{
|
||||||
|
return (mainTableName.equals(table) || aliasToTableNameMap.containsValue(table));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Check if the given tableOrAlias exists in the query - but note, if a table
|
||||||
|
** is in the query, but with an alias, then it would not be found by this method.
|
||||||
|
**
|
||||||
|
** e.g., Given:
|
||||||
|
** FROM `order` INNER JOIN line_item li
|
||||||
|
** hasAliasOrTable("order") => false
|
||||||
|
** hasAliasOrTable("li") => true
|
||||||
|
** hasAliasOrTable("line_item") => false
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean hasAliasOrTable(String tableOrAlias)
|
||||||
|
{
|
||||||
|
return (mainTableName.equals(tableOrAlias) || aliasToTableNameMap.containsKey(tableOrAlias));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||||
@ -47,6 +48,13 @@ public class QueryInput extends AbstractTableActionInput
|
|||||||
private boolean shouldTranslatePossibleValues = false;
|
private boolean shouldTranslatePossibleValues = false;
|
||||||
private boolean shouldGenerateDisplayValues = false;
|
private boolean shouldGenerateDisplayValues = false;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this field - only applies if shouldTranslatePossibleValues is true. //
|
||||||
|
// if this field is null, then ALL possible value fields get translated. //
|
||||||
|
// if this field is non-null, then ONLY the fieldNames in this set will be translated. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
private Set<String> fieldsToTranslatePossibleValues;
|
||||||
|
|
||||||
private List<QueryJoin> queryJoins = null;
|
private List<QueryJoin> queryJoins = null;
|
||||||
|
|
||||||
|
|
||||||
@ -295,4 +303,37 @@ public class QueryInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for fieldsToTranslatePossibleValues
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Set<String> getFieldsToTranslatePossibleValues()
|
||||||
|
{
|
||||||
|
return fieldsToTranslatePossibleValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for fieldsToTranslatePossibleValues
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFieldsToTranslatePossibleValues(Set<String> fieldsToTranslatePossibleValues)
|
||||||
|
{
|
||||||
|
this.fieldsToTranslatePossibleValues = fieldsToTranslatePossibleValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for fieldsToTranslatePossibleValues
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryInput withFieldsToTranslatePossibleValues(Set<String> fieldsToTranslatePossibleValues)
|
||||||
|
{
|
||||||
|
this.fieldsToTranslatePossibleValues = fieldsToTranslatePossibleValues;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,17 +22,38 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
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.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Part of query (or count, aggregate) input, to do a Join as part of a query.
|
** Part of query (or count, aggregate) input, to do a Join as part of a query.
|
||||||
|
**
|
||||||
|
** Conceptually, when you're adding a QueryJoin to a query, you're adding a new
|
||||||
|
** table to the query - this is named the `joinTable` in this class. This table
|
||||||
|
** can be given an alias, which can be referenced in the rest of the query.
|
||||||
|
**
|
||||||
|
** Every joinTable needs to have a `baseTable` that it is "joined" with - e.g.,
|
||||||
|
** the table that the joinOn clauses link up with.
|
||||||
|
**
|
||||||
|
** However - the caller doesn't necessarily need to specify the `baseTable` -
|
||||||
|
** as the framework will look for Joins defined in the qInstance, and if an
|
||||||
|
** unambiguous one is found (between the joinTable and other tables in the
|
||||||
|
** query), then it'll use the "other" table in that Join as the baseTable.
|
||||||
|
**
|
||||||
|
** For use-cases where a baseTable has been included in a query multiple times,
|
||||||
|
** with aliases, then the baseTableOrAlias field must be set to the appropriate alias.
|
||||||
|
**
|
||||||
|
** If there are multiple Joins defined between the base & join tables, then the
|
||||||
|
** specific joinMetaData to use must be set. The joinMetaData field can also be
|
||||||
|
** used instead of specify joinTable and baseTableOrAlias, but only for cases
|
||||||
|
** where the baseTable is not an alias.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QueryJoin
|
public class QueryJoin
|
||||||
{
|
{
|
||||||
private String leftTableOrAlias;
|
private String baseTableOrAlias;
|
||||||
private String rightTable;
|
private String joinTable;
|
||||||
private QJoinMetaData joinMetaData;
|
private QJoinMetaData joinMetaData;
|
||||||
private String alias;
|
private String alias;
|
||||||
private boolean select = false;
|
private boolean select = false;
|
||||||
@ -59,19 +80,42 @@ public class QueryJoin
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Constructor
|
** Constructor that only takes a joinTable. Unless you also set the baseTableOrAlias,
|
||||||
|
** the framework will attempt to ascertain the baseTableOrAlias, based on Joins
|
||||||
|
** defined in the instance and other tables in the query.
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QueryJoin(String leftTableOrAlias, String rightTable)
|
public QueryJoin(String joinTable)
|
||||||
{
|
{
|
||||||
this.leftTableOrAlias = leftTableOrAlias;
|
this.joinTable = joinTable;
|
||||||
this.rightTable = rightTable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Constructor
|
** Constructor that takes baseTableOrAlias and joinTable. Useful if it's not
|
||||||
|
** explicitly clear what the base table should be just from the joinTable. e.g.,
|
||||||
|
** if the baseTable has an alias, or if there's more than 1 join in the instance
|
||||||
|
** that matches the joinTable and the other tables in the query.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin(String baseTableOrAlias, String joinTable)
|
||||||
|
{
|
||||||
|
this.baseTableOrAlias = baseTableOrAlias;
|
||||||
|
this.joinTable = joinTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor that takes a joinMetaData - the rightTable in the joinMetaData will
|
||||||
|
** be used as the joinTable. The leftTable in the joinMetaData will be used as
|
||||||
|
** the baseTable.
|
||||||
|
**
|
||||||
|
** This is probably (only?) what you want to use if you have a table that joins
|
||||||
|
** more than once to another table (e.g., order.shipToCustomerId and order.billToCustomerId).
|
||||||
|
**
|
||||||
|
** Alternatively, you could just do new QueryJoin("customer").withJoinMetaData("orderJoinShipToCustomer").
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QueryJoin(QJoinMetaData joinMetaData)
|
public QueryJoin(QJoinMetaData joinMetaData)
|
||||||
@ -82,68 +126,68 @@ public class QueryJoin
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for leftTableOrAlias
|
** Getter for baseTableOrAlias
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public String getLeftTableOrAlias()
|
public String getBaseTableOrAlias()
|
||||||
{
|
{
|
||||||
return leftTableOrAlias;
|
return baseTableOrAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for leftTableOrAlias
|
** Setter for baseTableOrAlias
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setLeftTableOrAlias(String leftTableOrAlias)
|
public void setBaseTableOrAlias(String baseTableOrAlias)
|
||||||
{
|
{
|
||||||
this.leftTableOrAlias = leftTableOrAlias;
|
this.baseTableOrAlias = baseTableOrAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter for leftTableOrAlias
|
** Fluent setter for baseTableOrAlias
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QueryJoin withLeftTableOrAlias(String leftTableOrAlias)
|
public QueryJoin withBaseTableOrAlias(String baseTableOrAlias)
|
||||||
{
|
{
|
||||||
this.leftTableOrAlias = leftTableOrAlias;
|
this.baseTableOrAlias = baseTableOrAlias;
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for rightTable
|
** Getter for joinTable
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public String getRightTable()
|
public String getJoinTable()
|
||||||
{
|
{
|
||||||
return rightTable;
|
return joinTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for rightTable
|
** Setter for joinTable
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setRightTable(String rightTable)
|
public void setJoinTable(String joinTable)
|
||||||
{
|
{
|
||||||
this.rightTable = rightTable;
|
this.joinTable = joinTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter for rightTable
|
** Fluent setter for joinTable
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QueryJoin withRightTable(String rightTable)
|
public QueryJoin withJoinTable(String joinTable)
|
||||||
{
|
{
|
||||||
this.rightTable = rightTable;
|
this.joinTable = joinTable;
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,13 +264,13 @@ public class QueryJoin
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public String getAliasOrRightTable()
|
public String getJoinTableOrItsAlias()
|
||||||
{
|
{
|
||||||
if(StringUtils.hasContent(alias))
|
if(StringUtils.hasContent(alias))
|
||||||
{
|
{
|
||||||
return (alias);
|
return (alias);
|
||||||
}
|
}
|
||||||
return (rightTable);
|
return (joinTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -282,12 +326,13 @@ public class QueryJoin
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setJoinMetaData(QJoinMetaData joinMetaData)
|
public void setJoinMetaData(QJoinMetaData joinMetaData)
|
||||||
{
|
{
|
||||||
|
Objects.requireNonNull(joinMetaData, "JoinMetaData was null.");
|
||||||
this.joinMetaData = joinMetaData;
|
this.joinMetaData = joinMetaData;
|
||||||
|
|
||||||
if(!StringUtils.hasContent(this.leftTableOrAlias) && !StringUtils.hasContent(this.rightTable))
|
if(!StringUtils.hasContent(this.baseTableOrAlias) && !StringUtils.hasContent(this.joinTable))
|
||||||
{
|
{
|
||||||
setLeftTableOrAlias(joinMetaData.getLeftTable());
|
setBaseTableOrAlias(joinMetaData.getLeftTable());
|
||||||
setRightTable(joinMetaData.getRightTable());
|
setJoinTable(joinMetaData.getRightTable());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,6 +304,6 @@ public class QJoinMetaData
|
|||||||
{
|
{
|
||||||
throw (new IllegalStateException("Missing either a left or right table name when trying to set inferred name for join"));
|
throw (new IllegalStateException("Missing either a left or right table name when trying to set inferred name for join"));
|
||||||
}
|
}
|
||||||
return (withName(getLeftTable() + "Join" + getRightTable()));
|
return (withName(getLeftTable() + "Join" + StringUtils.ucFirst(getRightTable())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -466,6 +466,22 @@ public class QReportView implements Cloneable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter to add a single column
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportView withColumn(QReportField column)
|
||||||
|
{
|
||||||
|
if(this.columns == null)
|
||||||
|
{
|
||||||
|
this.columns = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.columns.add(column);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for orderByFields
|
** Getter for orderByFields
|
||||||
**
|
**
|
||||||
|
@ -28,6 +28,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
@ -39,6 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -53,9 +55,11 @@ public class MemoryRecordStore
|
|||||||
|
|
||||||
private static boolean collectStatistics = false;
|
private static boolean collectStatistics = false;
|
||||||
|
|
||||||
|
public static final String STAT_QUERIES_RAN = "queriesRan";
|
||||||
|
|
||||||
private static final Map<String, Integer> statistics = Collections.synchronizedMap(new HashMap<>());
|
private static final Map<String, Integer> statistics = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
public static final String STAT_QUERIES_RAN = "queriesRan";
|
public static final ListingHash<Class<? extends AbstractActionInput>, AbstractActionInput> actionInputs = new ListingHash<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -114,7 +118,7 @@ public class MemoryRecordStore
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public List<QRecord> query(QueryInput input)
|
public List<QRecord> query(QueryInput input)
|
||||||
{
|
{
|
||||||
incrementStatistic(STAT_QUERIES_RAN);
|
incrementStatistic(input);
|
||||||
|
|
||||||
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
|
Map<Serializable, QRecord> tableData = getTableData(input.getTable());
|
||||||
List<QRecord> records = new ArrayList<>();
|
List<QRecord> records = new ArrayList<>();
|
||||||
@ -295,6 +299,24 @@ public class MemoryRecordStore
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Increment a statistic
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void incrementStatistic(AbstractActionInput input)
|
||||||
|
{
|
||||||
|
if(collectStatistics)
|
||||||
|
{
|
||||||
|
actionInputs.add(input.getClass(), input);
|
||||||
|
if(input instanceof QueryInput)
|
||||||
|
{
|
||||||
|
incrementStatistic(STAT_QUERIES_RAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Increment a statistic
|
** Increment a statistic
|
||||||
**
|
**
|
||||||
@ -317,6 +339,7 @@ public class MemoryRecordStore
|
|||||||
public static void resetStatistics()
|
public static void resetStatistics()
|
||||||
{
|
{
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
actionInputs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -330,4 +353,15 @@ public class MemoryRecordStore
|
|||||||
return statistics;
|
return statistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for the actionInputs that were recorded - only while collectStatistics
|
||||||
|
** was true.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static ListingHash<Class<? extends AbstractActionInput>, AbstractActionInput> getActionInputs()
|
||||||
|
{
|
||||||
|
return (actionInputs);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ public class SleepUtils
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
long millisToSleep = end - System.currentTimeMillis();
|
long millisToSleep = end - System.currentTimeMillis();
|
||||||
Thread.sleep(millisToSleep);
|
Thread.sleep(Math.max(0, millisToSleep)); // avoid negative sleep, which fails.
|
||||||
}
|
}
|
||||||
catch(InterruptedException e)
|
catch(InterruptedException e)
|
||||||
{
|
{
|
||||||
|
@ -554,4 +554,33 @@ public class GenerateReportActionTest
|
|||||||
assertThat(row).containsOnlyKeys("Birth Date");
|
assertThat(row).containsOnlyKeys("Birth Date");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testReportWithPossibleValueColumns() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
|
||||||
|
insertPersonRecords(qInstance);
|
||||||
|
|
||||||
|
ReportInput reportInput = new ReportInput(qInstance);
|
||||||
|
reportInput.setSession(new QSession());
|
||||||
|
reportInput.setReportName(TestUtils.REPORT_NAME_PERSON_SIMPLE);
|
||||||
|
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
|
||||||
|
reportInput.setReportOutputStream(new ByteArrayOutputStream());
|
||||||
|
new GenerateReportAction().execute(reportInput);
|
||||||
|
|
||||||
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("Simple Report");
|
||||||
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
|
Map<String, String> row = iterator.next();
|
||||||
|
assertThat(row).containsKeys("Id", "First Name", "Last Name", "Home State Id", "Home State Name");
|
||||||
|
|
||||||
|
row = iterator.next();
|
||||||
|
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||||
|
assertThat(row.get("Home State Name")).isEqualTo("IL");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
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.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
@ -188,7 +189,7 @@ public class QPossibleValueTranslatorTest
|
|||||||
);
|
);
|
||||||
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
QTableMetaData personTable = qInstance.getTable(TestUtils.TABLE_NAME_PERSON);
|
||||||
MemoryRecordStore.resetStatistics();
|
MemoryRecordStore.resetStatistics();
|
||||||
possibleValueTranslator.primePvsCache(personTable, personRecords, null); // todo - test non-null queryJoins
|
possibleValueTranslator.primePvsCache(personTable, personRecords, null, null); // todo - test non-null queryJoins
|
||||||
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
assertEquals(1, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN), "Should only run 1 query");
|
||||||
possibleValueTranslator.translatePossibleValue(shapeField, 1);
|
possibleValueTranslator.translatePossibleValue(shapeField, 1);
|
||||||
possibleValueTranslator.translatePossibleValue(shapeField, 2);
|
possibleValueTranslator.translatePossibleValue(shapeField, 2);
|
||||||
@ -360,4 +361,101 @@ public class QPossibleValueTranslatorTest
|
|||||||
assertEquals("MO", records.get(1).getDisplayValue("homeStateId"));
|
assertEquals("MO", records.get(1).getDisplayValue("homeStateId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testPossibleValueWithSecondaryPossibleValueLabel() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
|
||||||
|
qInstance.addTable(new QTableMetaData()
|
||||||
|
.withName("city")
|
||||||
|
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("regionId", QFieldType.INTEGER).withPossibleValueSourceName("region")));
|
||||||
|
|
||||||
|
qInstance.addTable(new QTableMetaData()
|
||||||
|
.withName("region")
|
||||||
|
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withRecordLabelFormat("%s of %s")
|
||||||
|
.withRecordLabelFields("name", "countryId")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("countryId", QFieldType.INTEGER).withPossibleValueSourceName("country")));
|
||||||
|
|
||||||
|
qInstance.addTable(new QTableMetaData()
|
||||||
|
.withName("country")
|
||||||
|
.withBackendName(TestUtils.MEMORY_BACKEND_NAME)
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withRecordLabelFormat("%s")
|
||||||
|
.withRecordLabelFields("name")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("name", QFieldType.STRING)));
|
||||||
|
|
||||||
|
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||||
|
.withName("region")
|
||||||
|
.withType(QPossibleValueSourceType.TABLE)
|
||||||
|
.withTableName("region")
|
||||||
|
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY));
|
||||||
|
|
||||||
|
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||||
|
.withName("country")
|
||||||
|
.withType(QPossibleValueSourceType.TABLE)
|
||||||
|
.withTableName("country")
|
||||||
|
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY));
|
||||||
|
|
||||||
|
List<QRecord> regions = List.of(new QRecord().withValue("id", 11).withValue("name", "Missouri").withValue("countryId", 111));
|
||||||
|
List<QRecord> countries = List.of(new QRecord().withValue("id", 111).withValue("name", "U.S.A"));
|
||||||
|
|
||||||
|
TestUtils.insertRecords(qInstance, qInstance.getTable("region"), regions);
|
||||||
|
TestUtils.insertRecords(qInstance, qInstance.getTable("country"), countries);
|
||||||
|
|
||||||
|
MemoryRecordStore.resetStatistics();
|
||||||
|
MemoryRecordStore.setCollectStatistics(true);
|
||||||
|
|
||||||
|
QPossibleValueTranslator possibleValueTranslator = new QPossibleValueTranslator(qInstance, new QSession());
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// verify that if we run w/ an empty set for the param limitedToFieldNames, that we do NOT translate the regionId //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List<QRecord> cities = List.of(new QRecord().withValue("id", 1).withValue("name", "St. Louis").withValue("regionId", 11));
|
||||||
|
possibleValueTranslator.translatePossibleValuesInRecords(qInstance.getTable("city"), cities, null, Set.of());
|
||||||
|
assertNull(cities.get(0).getDisplayValue("regionId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// ditto a set that contains something, but not the field in question //
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List<QRecord> cities = List.of(new QRecord().withValue("id", 1).withValue("name", "St. Louis").withValue("regionId", 11));
|
||||||
|
possibleValueTranslator.translatePossibleValuesInRecords(qInstance.getTable("city"), cities, null, Set.of("foobar"));
|
||||||
|
assertNull(cities.get(0).getDisplayValue("regionId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// now re-run, w/ regionId - and we should see it get translated - and - the possible-value that it uses (countryId) as part of its label also gets translated. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List<QRecord> cities = List.of(new QRecord().withValue("id", 1).withValue("name", "St. Louis").withValue("regionId", 11));
|
||||||
|
possibleValueTranslator.translatePossibleValuesInRecords(qInstance.getTable("city"), cities, null, Set.of("regionId"));
|
||||||
|
assertEquals("Missouri of U.S.A", cities.get(0).getDisplayValue("regionId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// finally, verify that a null limitedToFieldNames means to translate them all //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List<QRecord> cities = List.of(new QRecord().withValue("id", 1).withValue("name", "St. Louis").withValue("regionId", 11));
|
||||||
|
possibleValueTranslator.translatePossibleValuesInRecords(qInstance.getTable("city"), cities, null, null);
|
||||||
|
assertEquals("Missouri of U.S.A", cities.get(0).getDisplayValue("regionId"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||||
@ -141,7 +140,8 @@ public class TestUtils
|
|||||||
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
|
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
|
||||||
public static final String TABLE_NAME_BASEPULL = "basepullTest";
|
public static final String TABLE_NAME_BASEPULL = "basepullTest";
|
||||||
public static final String REPORT_NAME_SHAPES_PERSON = "shapesPersonReport";
|
public static final String REPORT_NAME_SHAPES_PERSON = "shapesPersonReport";
|
||||||
public static final String REPORT_NAME_PERSON_JOIN_SHAPE = "simplePersonReport";
|
public static final String REPORT_NAME_PERSON_SIMPLE = "simplePersonReport";
|
||||||
|
public static final String REPORT_NAME_PERSON_JOIN_SHAPE = "personJoinShapeReport";
|
||||||
|
|
||||||
public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type
|
public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type
|
||||||
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type
|
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type
|
||||||
@ -195,6 +195,7 @@ public class TestUtils
|
|||||||
qInstance.addReport(defineShapesPersonsReport());
|
qInstance.addReport(defineShapesPersonsReport());
|
||||||
qInstance.addProcess(defineShapesPersonReportProcess());
|
qInstance.addProcess(defineShapesPersonReportProcess());
|
||||||
qInstance.addReport(definePersonJoinShapeReport());
|
qInstance.addReport(definePersonJoinShapeReport());
|
||||||
|
qInstance.addReport(definePersonSimpleReport());
|
||||||
|
|
||||||
qInstance.addAutomationProvider(definePollingAutomationProvider());
|
qInstance.addAutomationProvider(definePollingAutomationProvider());
|
||||||
|
|
||||||
@ -1113,6 +1114,32 @@ public class TestUtils
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QReportMetaData definePersonSimpleReport()
|
||||||
|
{
|
||||||
|
return new QReportMetaData()
|
||||||
|
.withName(REPORT_NAME_PERSON_SIMPLE)
|
||||||
|
.withDataSource(
|
||||||
|
new QReportDataSource()
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
)
|
||||||
|
.withView(new QReportView()
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withLabel("Simple Report")
|
||||||
|
.withColumns(List.of(
|
||||||
|
new QReportField("id"),
|
||||||
|
new QReportField("firstName"),
|
||||||
|
new QReportField("lastName"),
|
||||||
|
new QReportField("homeStateId").withLabel("Home State Id"),
|
||||||
|
new QReportField("homeStateName").withSourceFieldName("homeStateId").withShowPossibleValueLabel(true).withLabel("Home State Name")
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -183,38 +183,48 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
|
|
||||||
for(QueryJoin queryJoin : joinsContext.getQueryJoins())
|
for(QueryJoin queryJoin : joinsContext.getQueryJoins())
|
||||||
{
|
{
|
||||||
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
|
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
|
||||||
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
|
|
||||||
rs.append(" ").append(queryJoin.getType()).append(" JOIN ")
|
rs.append(" ").append(queryJoin.getType()).append(" JOIN ")
|
||||||
.append(escapeIdentifier(getTableName(joinTable)))
|
.append(escapeIdentifier(getTableName(joinTable)))
|
||||||
.append(" AS ").append(escapeIdentifier(tableNameOrAlias));
|
.append(" AS ").append(escapeIdentifier(tableNameOrAlias));
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// find the join in the instance, to see the 'on' clause //
|
// find the join in the instance, to set the 'on' clause //
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
List<String> joinClauseList = new ArrayList<>();
|
List<String> joinClauseList = new ArrayList<>();
|
||||||
String leftTableName = joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getLeftTableOrAlias());
|
String baseTableName = joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getBaseTableOrAlias());
|
||||||
QJoinMetaData joinMetaData = Objects.requireNonNullElseGet(queryJoin.getJoinMetaData(), () ->
|
QJoinMetaData joinMetaData = Objects.requireNonNullElseGet(queryJoin.getJoinMetaData(), () ->
|
||||||
{
|
{
|
||||||
QJoinMetaData found = findJoinMetaData(instance, leftTableName, queryJoin.getRightTable());
|
QJoinMetaData found = findJoinMetaData(instance, joinsContext, baseTableName, queryJoin.getJoinTable());
|
||||||
if(found == null)
|
if(found == null)
|
||||||
{
|
{
|
||||||
throw (new RuntimeException("Could not find a join between tables [" + leftTableName + "][" + queryJoin.getRightTable() + "]"));
|
throw (new RuntimeException("Could not find a join between tables [" + baseTableName + "][" + queryJoin.getJoinTable() + "]"));
|
||||||
}
|
}
|
||||||
return (found);
|
return (found);
|
||||||
});
|
});
|
||||||
|
|
||||||
for(JoinOn joinOn : joinMetaData.getJoinOns())
|
for(JoinOn joinOn : joinMetaData.getJoinOns())
|
||||||
{
|
{
|
||||||
QTableMetaData leftTable = instance.getTable(joinMetaData.getLeftTable());
|
QTableMetaData leftTable = instance.getTable(joinMetaData.getLeftTable());
|
||||||
QTableMetaData rightTable = instance.getTable(joinMetaData.getRightTable());
|
QTableMetaData rightTable = instance.getTable(joinMetaData.getRightTable());
|
||||||
|
|
||||||
String leftTableOrAlias = queryJoin.getLeftTableOrAlias();
|
String baseTableOrAlias = queryJoin.getBaseTableOrAlias();
|
||||||
String aliasOrRightTable = queryJoin.getAliasOrRightTable();
|
if(baseTableOrAlias == null)
|
||||||
|
{
|
||||||
|
baseTableOrAlias = leftTable.getName();
|
||||||
|
if(!joinsContext.hasAliasOrTable(baseTableOrAlias))
|
||||||
|
{
|
||||||
|
throw (new RuntimeException("Could not find a table or alias [" + baseTableOrAlias + "] in query. May need to be more specific setting up QueryJoins."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
joinClauseList.add(escapeIdentifier(leftTableOrAlias)
|
String joinTableOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
|
|
||||||
|
joinClauseList.add(escapeIdentifier(baseTableOrAlias)
|
||||||
+ "." + escapeIdentifier(getColumnName(leftTable.getField(joinOn.getLeftField())))
|
+ "." + escapeIdentifier(getColumnName(leftTable.getField(joinOn.getLeftField())))
|
||||||
+ " = " + escapeIdentifier(aliasOrRightTable)
|
+ " = " + escapeIdentifier(joinTableOrAlias)
|
||||||
+ "." + escapeIdentifier(getColumnName((rightTable.getField(joinOn.getRightField())))));
|
+ "." + escapeIdentifier(getColumnName((rightTable.getField(joinOn.getRightField())))));
|
||||||
}
|
}
|
||||||
rs.append(" ON ").append(StringUtils.join(" AND ", joinClauseList));
|
rs.append(" ON ").append(StringUtils.join(" AND ", joinClauseList));
|
||||||
@ -228,22 +238,49 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private QJoinMetaData findJoinMetaData(QInstance instance, String leftTable, String rightTable)
|
private QJoinMetaData findJoinMetaData(QInstance instance, JoinsContext joinsContext, String baseTableName, String joinTableName)
|
||||||
{
|
{
|
||||||
List<QJoinMetaData> matches = new ArrayList<>();
|
List<QJoinMetaData> matches = new ArrayList<>();
|
||||||
for(QJoinMetaData join : instance.getJoins().values())
|
if(baseTableName != null)
|
||||||
{
|
{
|
||||||
if(join.getLeftTable().equals(leftTable) && join.getRightTable().equals(rightTable))
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// if query specified a left-table, look for a join between left & right //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
for(QJoinMetaData join : instance.getJoins().values())
|
||||||
{
|
{
|
||||||
matches.add(join);
|
if(join.getLeftTable().equals(baseTableName) && join.getRightTable().equals(joinTableName))
|
||||||
}
|
{
|
||||||
|
matches.add(join);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// look in both directions! //
|
// look in both directions! //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
if(join.getRightTable().equals(leftTable) && join.getLeftTable().equals(rightTable))
|
if(join.getRightTable().equals(baseTableName) && join.getLeftTable().equals(joinTableName))
|
||||||
|
{
|
||||||
|
matches.add(join.flip());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if query didn't specify a left-table, then look for any join to the right table //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
for(QJoinMetaData join : instance.getJoins().values())
|
||||||
{
|
{
|
||||||
matches.add(join.flip());
|
if(join.getRightTable().equals(joinTableName) && joinsContext.hasTable(join.getLeftTable()))
|
||||||
|
{
|
||||||
|
matches.add(join);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// look in both directions! //
|
||||||
|
//////////////////////////////
|
||||||
|
if(join.getLeftTable().equals(joinTableName) && joinsContext.hasTable(join.getRightTable()))
|
||||||
|
{
|
||||||
|
matches.add(join.flip());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +290,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
}
|
}
|
||||||
else if(matches.size() > 1)
|
else if(matches.size() > 1)
|
||||||
{
|
{
|
||||||
throw (new RuntimeException("More than 1 join was found between [" + leftTable + "] and [" + rightTable + "]. Specify which one in your QueryJoin."));
|
throw (new RuntimeException("More than 1 join was found between [" + baseTableName + "] and [" + joinTableName + "]. Specify which one in your QueryJoin."));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (null);
|
return (null);
|
||||||
|
@ -121,8 +121,8 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
{
|
{
|
||||||
if(queryJoin.getSelect())
|
if(queryJoin.getSelect())
|
||||||
{
|
{
|
||||||
QTableMetaData joinTable = queryInput.getInstance().getTable(queryJoin.getRightTable());
|
QTableMetaData joinTable = queryInput.getInstance().getTable(queryJoin.getJoinTable());
|
||||||
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
for(QFieldMetaData joinField : joinTable.getFields().values())
|
for(QFieldMetaData joinField : joinTable.getFields().values())
|
||||||
{
|
{
|
||||||
fieldList.add(joinField.clone().withName(tableNameOrAlias + "." + joinField.getName()));
|
fieldList.add(joinField.clone().withName(tableNameOrAlias + "." + joinField.getName()));
|
||||||
@ -201,11 +201,11 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
{
|
{
|
||||||
if(queryJoin.getSelect())
|
if(queryJoin.getSelect())
|
||||||
{
|
{
|
||||||
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
|
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
|
||||||
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
if(joinTable == null)
|
if(joinTable == null)
|
||||||
{
|
{
|
||||||
throw new QException("Requested join table [" + queryJoin.getRightTable() + "] is not a defined table.");
|
throw new QException("Requested join table [" + queryJoin.getJoinTable() + "] is not a defined table.");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<QFieldMetaData> joinFieldList = new ArrayList<>(joinTable.getFields().values());
|
List<QFieldMetaData> joinFieldList = new ArrayList<>(joinTable.getFields().values());
|
||||||
|
@ -32,6 +32,9 @@ 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.JoinType;
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||||
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.possiblevalues.PVSValueFormatAndFields;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
|
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
|
||||||
@ -90,6 +93,7 @@ public class TestUtils
|
|||||||
QInstance qInstance = new QInstance();
|
QInstance qInstance = new QInstance();
|
||||||
qInstance.addBackend(defineBackend());
|
qInstance.addBackend(defineBackend());
|
||||||
qInstance.addTable(defineTablePerson());
|
qInstance.addTable(defineTablePerson());
|
||||||
|
qInstance.addPossibleValueSource(definePvsPerson());
|
||||||
qInstance.addTable(defineTablePersonalIdCard());
|
qInstance.addTable(defineTablePersonalIdCard());
|
||||||
qInstance.addJoin(defineJoinPersonAndPersonalIdCard());
|
qInstance.addJoin(defineJoinPersonAndPersonalIdCard());
|
||||||
addOmsTablesAndJoins(qInstance);
|
addOmsTablesAndJoins(qInstance);
|
||||||
@ -135,6 +139,8 @@ public class TestUtils
|
|||||||
return new QTableMetaData()
|
return new QTableMetaData()
|
||||||
.withName(TABLE_NAME_PERSON)
|
.withName(TABLE_NAME_PERSON)
|
||||||
.withLabel("Person")
|
.withLabel("Person")
|
||||||
|
.withRecordLabelFormat("%s %s")
|
||||||
|
.withRecordLabelFields("firstName", "lastName")
|
||||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
.withPrimaryKeyField("id")
|
.withPrimaryKeyField("id")
|
||||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
@ -153,6 +159,21 @@ public class TestUtils
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QPossibleValueSource definePvsPerson()
|
||||||
|
{
|
||||||
|
return (new QPossibleValueSource()
|
||||||
|
.withName(TABLE_NAME_PERSON)
|
||||||
|
.withType(QPossibleValueSourceType.TABLE)
|
||||||
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
|
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Define a 1:1 table with Person.
|
** Define a 1:1 table with Person.
|
||||||
**
|
**
|
||||||
@ -196,24 +217,26 @@ public class TestUtils
|
|||||||
private static void addOmsTablesAndJoins(QInstance qInstance)
|
private static void addOmsTablesAndJoins(QInstance qInstance)
|
||||||
{
|
{
|
||||||
qInstance.addTable(defineBaseTable(TABLE_NAME_STORE, "store")
|
qInstance.addTable(defineBaseTable(TABLE_NAME_STORE, "store")
|
||||||
|
.withRecordLabelFormat("%s")
|
||||||
|
.withRecordLabelFields("name")
|
||||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||||
);
|
);
|
||||||
|
|
||||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER, "order")
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER, "order")
|
||||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id"))
|
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||||
.withField(new QFieldMetaData("billToPersonId", QFieldType.INTEGER).withBackendName("bill_to_person_id"))
|
.withField(new QFieldMetaData("billToPersonId", QFieldType.INTEGER).withBackendName("bill_to_person_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||||
.withField(new QFieldMetaData("shipToPersonId", QFieldType.INTEGER).withBackendName("ship_to_person_id"))
|
.withField(new QFieldMetaData("shipToPersonId", QFieldType.INTEGER).withBackendName("ship_to_person_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||||
);
|
);
|
||||||
|
|
||||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
|
||||||
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id"))
|
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||||
);
|
);
|
||||||
|
|
||||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER_LINE, "order_line")
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER_LINE, "order_line")
|
||||||
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
|
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
|
||||||
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id"))
|
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||||
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER))
|
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -266,6 +289,12 @@ public class TestUtils
|
|||||||
.withJoinOn(new JoinOn("storeId", "storeId"))
|
.withJoinOn(new JoinOn("storeId", "storeId"))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||||
|
.withName("store")
|
||||||
|
.withType(QPossibleValueSourceType.TABLE)
|
||||||
|
.withTableName(TABLE_NAME_STORE)
|
||||||
|
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
|||||||
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.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@ -611,7 +612,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
void testOneToOneInnerJoinWithoutWhere() throws QException
|
void testOneToOneInnerJoinWithoutWhere() throws QException
|
||||||
{
|
{
|
||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true));
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true));
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(3, queryOutput.getRecords().size(), "Join query should find 3 rows");
|
assertEquals(3, queryOutput.getRecords().size(), "Join query should find 3 rows");
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("personalIdCard.idNumber").equals("19800531"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("personalIdCard.idNumber").equals("19800531"));
|
||||||
@ -628,7 +629,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
void testOneToOneLeftJoinWithoutWhere() throws QException
|
void testOneToOneLeftJoinWithoutWhere() throws QException
|
||||||
{
|
{
|
||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.LEFT).withSelect(true));
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.LEFT).withSelect(true));
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(5, queryOutput.getRecords().size(), "Left Join query should find 5 rows");
|
assertEquals(5, queryOutput.getRecords().size(), "Left Join query should find 5 rows");
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("personalIdCard.idNumber").equals("19800531"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("personalIdCard.idNumber").equals("19800531"));
|
||||||
@ -647,7 +648,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
void testOneToOneRightJoinWithoutWhere() throws QException
|
void testOneToOneRightJoinWithoutWhere() throws QException
|
||||||
{
|
{
|
||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.RIGHT).withSelect(true));
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.RIGHT).withSelect(true));
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(6, queryOutput.getRecords().size(), "Right Join query should find 6 rows");
|
assertEquals(6, queryOutput.getRecords().size(), "Right Join query should find 6 rows");
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("personalIdCard.idNumber").equals("19800531"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("personalIdCard.idNumber").equals("19800531"));
|
||||||
@ -667,7 +668,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
void testOneToOneInnerJoinWithWhere() throws QException
|
void testOneToOneInnerJoinWithWhere() throws QException
|
||||||
{
|
{
|
||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true));
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true));
|
||||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber", QCriteriaOperator.STARTS_WITH, "1980")));
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber", QCriteriaOperator.STARTS_WITH, "1980")));
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(2, queryOutput.getRecords().size(), "Join query should find 2 rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Join query should find 2 rows");
|
||||||
@ -685,7 +686,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
queryInput.withQueryJoin(new QueryJoin(qInstance.getJoin(TestUtils.TABLE_NAME_PERSON + "Join" + TestUtils.TABLE_NAME_PERSONAL_ID_CARD)).withSelect(true));
|
queryInput.withQueryJoin(new QueryJoin(qInstance.getJoin(TestUtils.TABLE_NAME_PERSON + "Join" + StringUtils.ucFirst(TestUtils.TABLE_NAME_PERSONAL_ID_CARD))).withSelect(true));
|
||||||
queryInput.setFilter(new QQueryFilter().withOrderBy(new QFilterOrderBy(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber")));
|
queryInput.setFilter(new QQueryFilter().withOrderBy(new QFilterOrderBy(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber")));
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(3, queryOutput.getRecords().size(), "Join query should find 3 rows");
|
assertEquals(3, queryOutput.getRecords().size(), "Join query should find 3 rows");
|
||||||
@ -755,7 +756,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
|
|
||||||
QueryInput queryInput = new QueryInput(TestUtils.defineInstance(), new QSession());
|
QueryInput queryInput = new QueryInput(TestUtils.defineInstance(), new QSession());
|
||||||
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER_LINE);
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER_LINE);
|
||||||
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER_LINE, TestUtils.TABLE_NAME_ORDER).withSelect(true));
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER).withSelect(true));
|
||||||
|
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(orderLineCount.get(), queryOutput.getRecords().size(), "# of rows found by query");
|
assertEquals(orderLineCount.get(), queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
@ -780,7 +781,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
// inner join on bill-to person should find 6 rows //
|
// inner join on bill-to person should find 6 rows //
|
||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
queryInput.withQueryJoins(List.of(new QueryJoin(TestUtils.TABLE_NAME_ORDER, TestUtils.TABLE_NAME_PERSON).withJoinMetaData(instance.getJoin("orderJoinBillToPerson")).withSelect(true)));
|
queryInput.withQueryJoins(List.of(new QueryJoin(TestUtils.TABLE_NAME_PERSON).withJoinMetaData(instance.getJoin("orderJoinBillToPerson")).withSelect(true)));
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(6, queryOutput.getRecords().size(), "# of rows found by query");
|
assertEquals(6, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
|
||||||
@ -829,6 +830,42 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
queryOutput = new QueryAction().execute(queryInput);
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
assertEquals(3, queryOutput.getRecords().size(), "# of rows found by query");
|
assertEquals(3, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
assertThat(queryOutput.getRecords().stream().map(r -> r.getValueString("billToPerson.firstName")).toList()).allMatch(p -> p.equals("Darin") || p.equals("James"));
|
assertThat(queryOutput.getRecords().stream().map(r -> r.getValueString("billToPerson.firstName")).toList()).allMatch(p -> p.equals("Darin") || p.equals("James"));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ensure we throw if either of the ambiguous joins from person to id-card doesn't specify its left-table //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withAlias("shipToPerson").withSelect(true),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withAlias("billToPerson").withSelect(true),
|
||||||
|
new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("billToIdCard").withSelect(true),
|
||||||
|
new QueryJoin("shipToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("shipToIdCard").withSelect(true)
|
||||||
|
));
|
||||||
|
assertThatThrownBy(() -> new QueryAction().execute(queryInput))
|
||||||
|
.hasRootCauseMessage("Could not find a table or alias [personTable] in query. May need to be more specific setting up QueryJoins.");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ensure we throw if either of the ambiguous joins from person to id-card doesn't specify its left-table //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withAlias("shipToPerson").withSelect(true),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withAlias("billToPerson").withSelect(true),
|
||||||
|
new QueryJoin("billToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("billToIdCard").withSelect(true),
|
||||||
|
new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("shipToIdCard").withSelect(true)
|
||||||
|
));
|
||||||
|
assertThatThrownBy(() -> new QueryAction().execute(queryInput))
|
||||||
|
.hasRootCauseMessage("Could not find a table or alias [personTable] in query. May need to be more specific setting up QueryJoins.");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// ensure we throw if we have a bogus alias name given as a left-side //
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withAlias("shipToPerson").withSelect(true),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withAlias("billToPerson").withSelect(true),
|
||||||
|
new QueryJoin("notATable", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("billToIdCard").withSelect(true),
|
||||||
|
new QueryJoin("shipToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("shipToIdCard").withSelect(true)
|
||||||
|
));
|
||||||
|
assertThatThrownBy(() -> new QueryAction().execute(queryInput))
|
||||||
|
.hasRootCauseMessage("Could not find a join between tables [notATable][personalIdCard]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.module.rdbms.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
|
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
||||||
|
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Do some tests on the qqq-backend-core GenerateReportAction, that are kinda
|
||||||
|
** hard to do in a backend that doesn't support joins, but that we can do in
|
||||||
|
** RDBMS.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class GenerateReportActionRDBMSTest extends RDBMSActionTest
|
||||||
|
{
|
||||||
|
private static final String TEST_REPORT = "testReport";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach() throws Exception
|
||||||
|
{
|
||||||
|
super.primeTestDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testTwoJoinsToSameTable() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QReportMetaData report = new QReportMetaData()
|
||||||
|
.withName(TEST_REPORT)
|
||||||
|
.withDataSource(new QReportDataSource()
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_ORDER)
|
||||||
|
.withQueryJoin(new QueryJoin(qInstance.getJoin("orderJoinBillToPerson")).withAlias("billToPerson").withType(QueryJoin.Type.LEFT).withSelect(true))
|
||||||
|
.withQueryJoin(new QueryJoin(qInstance.getJoin("orderJoinShipToPerson")).withAlias("shipToPerson").withType(QueryJoin.Type.LEFT).withSelect(true))
|
||||||
|
)
|
||||||
|
.withView(new QReportView()
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withColumn(new QReportField("id"))
|
||||||
|
.withColumn(new QReportField("storeId").withLabel("Store Id"))
|
||||||
|
.withColumn(new QReportField("storeName").withShowPossibleValueLabel(true).withSourceFieldName("storeId").withLabel("Store Name"))
|
||||||
|
.withColumn(new QReportField("billToPerson.id"))
|
||||||
|
.withColumn(new QReportField("billToPerson.firstName").withLabel("Bill To First Name"))
|
||||||
|
.withColumn(new QReportField("billToPersonName").withShowPossibleValueLabel(true).withSourceFieldName("billToPersonId"))
|
||||||
|
.withColumn(new QReportField("shipToPerson.id"))
|
||||||
|
.withColumn(new QReportField("shipToPerson.firstName").withLabel("Ship To First Name"))
|
||||||
|
.withColumn(new QReportField("shipToPersonName").withShowPossibleValueLabel(true).withSourceFieldName("billToPersonId"))
|
||||||
|
);
|
||||||
|
qInstance.addReport(report);
|
||||||
|
|
||||||
|
String csv = runReport(qInstance);
|
||||||
|
// System.out.println(csv);
|
||||||
|
|
||||||
|
assertEquals("""
|
||||||
|
"Id","Store Id","Store Name","Id","Bill To First Name","Bill To Person","Id","Ship To First Name","Bill To Person"
|
||||||
|
"1","1","Q-Mart","1","Darin","Darin Kelkhoff","1","Darin","Darin Kelkhoff"
|
||||||
|
"2","1","Q-Mart","1","Darin","Darin Kelkhoff","2","James","Darin Kelkhoff"
|
||||||
|
"3","1","Q-Mart","2","James","James Maes","3","Tim","James Maes"
|
||||||
|
"4","2","QQQ 'R' Us","4","Tyler","Tyler Samples","5","Garret","Tyler Samples"
|
||||||
|
"5","2","QQQ 'R' Us","5","Garret","Garret Richardson","4","Tyler","Garret Richardson"
|
||||||
|
"6","3","QDepot","5","Garret","Garret Richardson","","","Garret Richardson"
|
||||||
|
"7","3","QDepot","","","","5","Garret",""
|
||||||
|
"8","3","QDepot","","","","5","Garret",""
|
||||||
|
""", csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testTwoTablesWithSamePossibleValue() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QReportMetaData report = new QReportMetaData()
|
||||||
|
.withName(TEST_REPORT)
|
||||||
|
.withDataSource(new QReportDataSource()
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_ORDER_LINE)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER).withSelect(true))
|
||||||
|
.withQueryFilter(new QQueryFilter(new QFilterCriteria("storeId", QCriteriaOperator.NOT_EQUALS).withOtherFieldName("order.storeId")))
|
||||||
|
)
|
||||||
|
.withView(new QReportView()
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withColumn(new QReportField("storeId").withLabel("Line Item Store Id"))
|
||||||
|
.withColumn(new QReportField("storeName").withShowPossibleValueLabel(true).withSourceFieldName("storeId").withLabel("Line Item Store Name"))
|
||||||
|
.withColumn(new QReportField("order.storeId").withLabel("Order Store Id"))
|
||||||
|
.withColumn(new QReportField("order.storeName").withShowPossibleValueLabel(true).withSourceFieldName("order.storeId").withLabel("Order Store Name"))
|
||||||
|
);
|
||||||
|
qInstance.addReport(report);
|
||||||
|
|
||||||
|
String csv = runReport(qInstance);
|
||||||
|
// System.out.println(csv);
|
||||||
|
|
||||||
|
assertEquals("""
|
||||||
|
"Line Item Store Id","Line Item Store Name","Order Store Id","Order Store Name"
|
||||||
|
"2","QQQ 'R' Us","1","Q-Mart"
|
||||||
|
""", csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testPossibleValueThroughAlias() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QReportMetaData report = new QReportMetaData()
|
||||||
|
.withName(TEST_REPORT)
|
||||||
|
.withDataSource(new QReportDataSource()
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_ORDER_LINE)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ITEM).withAlias("i").withSelect(true))
|
||||||
|
)
|
||||||
|
.withView(new QReportView()
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withColumn(new QReportField("id").withLabel("Line Item Id"))
|
||||||
|
.withColumn(new QReportField("sku").withLabel("Item SKU"))
|
||||||
|
.withColumn(new QReportField("i.storeId").withLabel("Item Store Id"))
|
||||||
|
.withColumn(new QReportField("i.storeName").withShowPossibleValueLabel(true).withSourceFieldName("i.storeId").withLabel("Item Store Name"))
|
||||||
|
);
|
||||||
|
qInstance.addReport(report);
|
||||||
|
|
||||||
|
String csv = runReport(qInstance);
|
||||||
|
System.out.println(csv);
|
||||||
|
|
||||||
|
assertEquals("""
|
||||||
|
"Line Item Id","Item SKU","Item Store Id","Item Store Name"
|
||||||
|
"1","QM-1","1","Q-Mart"
|
||||||
|
"5","QM-1","1","Q-Mart"
|
||||||
|
"2","QM-2","1","Q-Mart"
|
||||||
|
"3","QM-3","1","Q-Mart"
|
||||||
|
"4","QRU-1","2","QQQ 'R' Us"
|
||||||
|
"6","QRU-1","2","QQQ 'R' Us"
|
||||||
|
"8","QRU-1","2","QQQ 'R' Us"
|
||||||
|
"7","QRU-2","2","QQQ 'R' Us"
|
||||||
|
"9","QD-1","3","QDepot"
|
||||||
|
"10","QD-1","3","QDepot"
|
||||||
|
"11","QD-1","3","QDepot"
|
||||||
|
""", csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private String runReport(QInstance qInstance) throws QException
|
||||||
|
{
|
||||||
|
ReportInput reportInput = new ReportInput(qInstance);
|
||||||
|
reportInput.setSession(new QSession());
|
||||||
|
reportInput.setReportName(TEST_REPORT);
|
||||||
|
reportInput.setReportFormat(ReportFormat.CSV);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
reportInput.setReportOutputStream(outputStream);
|
||||||
|
new GenerateReportAction().execute(reportInput);
|
||||||
|
return (outputStream.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user