mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-09-23 00:40:51 +00:00
Feature/table personalization (#224)
* fix: allow validateFieldNames in AggregateAction to see implicit join tables This involved constructing a JoinsContext inside the validation method - which (as it originally should have been) was moved out of QueryAction, into new SelectionValidationHelper. * fix(joinsContext): throw explicit exception if a QueryJoin has an unrecognized table name * fix(query): for query's usage of SelectionValidationHelper, require that queryJoin objects are marked as for being selected; avoid NPE on null join table. * feat(cloning): give default implementation (e.g., no longer abstract) for finishClone, to help with backward compat ability * refactor: delete unused code * test(insertAction): Add assertion about inserted-ids being put on newly inserted associated records This change was added in previous commits in this branch on InsertAction, so this gets some test coverage in on it as well. * fix(queryAction): clone the queryJoins that are passed into the JoinsContext in SelectionValidationHelper too, so it should not be able to impact anything (since apparently it has side effects all over the place).
This commit is contained in:
@@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.personalization.TableMetaDataPersonalizerAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.FilterValidationHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.SelectionValidationHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@@ -129,7 +130,7 @@ public class AggregateAction
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(inputFieldNames))
|
||||
{
|
||||
List<String> unrecognizedFieldNames = QueryAction.getUnrecognizedFieldNames(aggregateInput, inputFieldNames);
|
||||
List<String> unrecognizedFieldNames = SelectionValidationHelper.getUnrecognizedFieldNames(aggregateInput, inputFieldNames);
|
||||
if(CollectionUtils.nullSafeHasContents(unrecognizedFieldNames))
|
||||
{
|
||||
throw (new QException("AggregateInput contained " + unrecognizedFieldNames.size() + " unrecognized field name" + StringUtils.plural(unrecognizedFieldNames) + ": " + StringUtils.join(",", unrecognizedFieldNames)));
|
||||
|
@@ -26,7 +26,6 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -43,19 +42,17 @@ import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipeBufferedWrappe
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.FilterValidationHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryActionCacheHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.SelectionValidationHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.personalization.TableMetaDataPersonalizerInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
|
||||
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.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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
@@ -195,7 +192,7 @@ public class QueryAction
|
||||
throw (new QException("An empty set of fieldNamesToInclude was given as queryInput, which is not allowed."));
|
||||
}
|
||||
|
||||
List<String> unrecognizedFieldNames = getUnrecognizedFieldNames(queryInput, fieldNamesToInclude);
|
||||
List<String> unrecognizedFieldNames = SelectionValidationHelper.getUnrecognizedFieldNames(queryInput, fieldNamesToInclude);
|
||||
|
||||
if(!unrecognizedFieldNames.isEmpty())
|
||||
{
|
||||
@@ -205,91 +202,6 @@ public class QueryAction
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
static List<String> getUnrecognizedFieldNames(QueryOrCountInputInterface queryInput, Set<String> fieldNames) throws QException
|
||||
{
|
||||
List<String> unrecognizedFieldNames = new ArrayList<>();
|
||||
Map<String, QTableMetaData> selectedQueryJoins = null;
|
||||
for(String fieldName : fieldNames)
|
||||
{
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
////////////////////////////////////////////////
|
||||
// handle names with dots - fields from joins //
|
||||
////////////////////////////////////////////////
|
||||
String[] parts = fieldName.split("\\.");
|
||||
if(parts.length != 2)
|
||||
{
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
String tableOrAlias = parts[0];
|
||||
String fieldNamePart = parts[1];
|
||||
|
||||
////////////////////////////////////////////
|
||||
// build map of queryJoins being selected //
|
||||
////////////////////////////////////////////
|
||||
if(selectedQueryJoins == null)
|
||||
{
|
||||
selectedQueryJoins = new HashMap<>();
|
||||
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryInput.getQueryJoins()))
|
||||
{
|
||||
if(queryJoin.getSelect())
|
||||
{
|
||||
String joinTableOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(queryJoin.getJoinTable());
|
||||
|
||||
/////////////////////////////////
|
||||
// personalize the join table! //
|
||||
/////////////////////////////////
|
||||
joinTable = TableMetaDataPersonalizerAction.execute(new TableMetaDataPersonalizerInput().withTableMetaData(joinTable).withInputSource(queryInput.getInputSource()));
|
||||
|
||||
if(joinTable != null)
|
||||
{
|
||||
selectedQueryJoins.put(joinTableOrAlias, joinTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!selectedQueryJoins.containsKey(tableOrAlias))
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// unrecognized tableOrAlias is an error //
|
||||
///////////////////////////////////////////
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
QTableMetaData joinTable = selectedQueryJoins.get(tableOrAlias);
|
||||
if(!joinTable.getFields().containsKey(fieldNamePart))
|
||||
{
|
||||
//////////////////////////////////////////////////////////
|
||||
// unrecognized field within the join table is an error //
|
||||
//////////////////////////////////////////////////////////
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// non-join fields - just ensure field name is in table's fields map //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
if(!queryInput.getTable().getFields().containsKey(fieldName))
|
||||
{
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return unrecognizedFieldNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** shorthand way to call for the most common use-case, when you just want the
|
||||
|
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.personalization.TableMetaDataPersonalizerAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.personalization.TableMetaDataPersonalizerInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||
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.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Utility to help query and aggregate actions validate the fieldNames being
|
||||
* selected, aggregated, grouped by.
|
||||
*******************************************************************************/
|
||||
public class SelectionValidationHelper
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
* For the given set of field names, and a query (or aggregate) input -
|
||||
* figure out if any field names (which may be joinTable (or alias) dot field
|
||||
* name) - figure out if any of those aren't recognized fields in the input
|
||||
* table or any tables that'll e joined.
|
||||
*
|
||||
* joins may either be explicit, via queryJoins in the input, else, they can
|
||||
* be "inferred" through a {@link JoinsContext}, which will look at the
|
||||
* filter and security fields.
|
||||
*
|
||||
* Note that table personalization is applied in here to the join tables
|
||||
* but it is assumed that input object has already has the table ran
|
||||
* through the personalize action.
|
||||
*
|
||||
* @param input assumed to be a QueryInput or AggregateInput - where the
|
||||
* table in it should have already gone through
|
||||
* {@link TableMetaDataPersonalizerAction}.
|
||||
* @param fieldNames assumed to have come from
|
||||
* {@link com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate}
|
||||
* or {@link com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy}
|
||||
* objects, or a {@link com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput#withFieldNamesToInclude(Set)}
|
||||
* @return list of field names that were unrecognized. empty if none.
|
||||
***************************************************************************/
|
||||
public static List<String> getUnrecognizedFieldNames(QueryOrCountInputInterface input, Set<String> fieldNames) throws QException
|
||||
{
|
||||
List<String> unrecognizedFieldNames = new ArrayList<>();
|
||||
Map<String, QTableMetaData> queryJoinsByNameOrAlias = null;
|
||||
for(String fieldName : fieldNames)
|
||||
{
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
////////////////////////////////////////////////
|
||||
// handle names with dots - fields from joins //
|
||||
////////////////////////////////////////////////
|
||||
String[] parts = fieldName.split("\\.");
|
||||
if(parts.length != 2)
|
||||
{
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
String tableOrAlias = parts[0];
|
||||
String fieldNamePart = parts[1];
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// build map of queryJoins by name or alias //
|
||||
//////////////////////////////////////////////
|
||||
if(queryJoinsByNameOrAlias == null)
|
||||
{
|
||||
queryJoinsByNameOrAlias = new HashMap<>();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// build a joinsContext, since it knows how to infer join tables that the user may not have specified in their input. //
|
||||
// but, since it can manipulate the query filter (e.g., adding security clauses) and queryJoins, pass it a clones //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter filterForJoinsContext = ObjectUtils.tryElse(() -> input.getFilter().clone(), new QQueryFilter());
|
||||
List<QueryJoin> queryJoinsForJoinsContext = new ArrayList<>();
|
||||
for(QueryJoin queryJoin : CollectionUtils.nonNullList(input.getQueryJoins()))
|
||||
{
|
||||
queryJoinsForJoinsContext.add(queryJoin.clone());
|
||||
}
|
||||
|
||||
JoinsContext joinsContext = new JoinsContext(QContext.getQInstance(), input.getTableName(), queryJoinsForJoinsContext, filterForJoinsContext);
|
||||
List<QueryJoin> queryJoins = joinsContext.getQueryJoins();
|
||||
|
||||
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||
{
|
||||
if(input instanceof QueryInput)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// for queries - only allow a join if it's marked as being selected //
|
||||
// for aggregates, joins don't have to be marked as such. //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
if(!queryJoin.getSelect())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String joinTableOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(queryJoin.getJoinTable());
|
||||
if(joinTable == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
// personalize the join table! //
|
||||
/////////////////////////////////
|
||||
joinTable = TableMetaDataPersonalizerAction.execute(new TableMetaDataPersonalizerInput().withTableMetaData(joinTable).withInputSource(input.getInputSource()));
|
||||
|
||||
if(joinTable != null)
|
||||
{
|
||||
queryJoinsByNameOrAlias.put(joinTableOrAlias, joinTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!queryJoinsByNameOrAlias.containsKey(tableOrAlias))
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// unrecognized tableOrAlias is an error //
|
||||
///////////////////////////////////////////
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
QTableMetaData joinTable = queryJoinsByNameOrAlias.get(tableOrAlias);
|
||||
if(!joinTable.getFields().containsKey(fieldNamePart))
|
||||
{
|
||||
//////////////////////////////////////////////////////////
|
||||
// unrecognized field within the join table is an error //
|
||||
//////////////////////////////////////////////////////////
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// non-join fields - just ensure field name is in table's fields map //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
if(!input.getTable().getFields().containsKey(fieldName))
|
||||
{
|
||||
unrecognizedFieldNames.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return unrecognizedFieldNames;
|
||||
}
|
||||
|
||||
}
|
@@ -996,10 +996,6 @@ public class QInstanceEnricher
|
||||
}
|
||||
}
|
||||
|
||||
String fieldsForHelpText = editableFields.stream()
|
||||
.map(QFieldMetaData::getLabel)
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
QBackendStepMetaData prepareFileUploadStep = new QBackendStepMetaData()
|
||||
.withName("prepareFileUpload")
|
||||
.withCode(new QCodeReference(BulkInsertPrepareFileUploadStep.class));
|
||||
|
@@ -918,8 +918,13 @@ public class JoinsContext
|
||||
*******************************************************************************/
|
||||
private void processQueryJoin(QueryJoin queryJoin) throws QException
|
||||
{
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(queryJoin.getJoinTable());
|
||||
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(queryJoin.getJoinTable());
|
||||
if(joinTable == null)
|
||||
{
|
||||
throw (new QException("Unrecognized name for join table: " + queryJoin.getJoinTable()));
|
||||
}
|
||||
|
||||
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||
if(aliasToTableNameMap.containsKey(tableNameOrAlias))
|
||||
{
|
||||
dumpDebug(false, true);
|
||||
|
@@ -22,8 +22,12 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -32,6 +36,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
*******************************************************************************/
|
||||
public abstract class QSupplementalTableMetaData implements Cloneable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QSupplementalTableMetaData.class);
|
||||
|
||||
private static Set<Class<?>> warnedAboutMissingFinishClones = new HashSet<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -110,7 +118,20 @@ public abstract class QSupplementalTableMetaData implements Cloneable
|
||||
* finish the cloning operation started in the base class. copy all state
|
||||
* from the subclass into the input clone (which can be safely casted to
|
||||
* the subclass's type, as it was obtained by super.clone())
|
||||
*
|
||||
* Rather than making this public and breaking all existing implementations
|
||||
* that don't have it - we're making it protected, with a one-time warning
|
||||
* if it isn't implemented in a subclass.
|
||||
***************************************************************************/
|
||||
protected abstract QSupplementalTableMetaData finishClone(QSupplementalTableMetaData abstractClone);
|
||||
protected QSupplementalTableMetaData finishClone(QSupplementalTableMetaData abstractClone)
|
||||
{
|
||||
if(!warnedAboutMissingFinishClones.contains(abstractClone.getClass()))
|
||||
{
|
||||
LOG.warn("Missing finishClone method in a subclass of QSupplementalTableMetaData.", logPair("className", abstractClone.getClass().getName()));
|
||||
warnedAboutMissingFinishClones.add(abstractClone.getClass());
|
||||
}
|
||||
|
||||
return (abstractClone);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperat
|
||||
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.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -92,6 +93,48 @@ class AggregateActionTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testJoins() throws QException
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// first 2 cases - supply a filter that allows us, through the JoinContext, to figure out that the Order table is joined in. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new AggregateAction().execute(new AggregateInput(TestUtils.TABLE_NAME_LINE_ITEM)
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria(TestUtils.TABLE_NAME_ORDER + ".storeId", QCriteriaOperator.IS_NOT_BLANK)))
|
||||
.withAggregate(new Aggregate(TestUtils.TABLE_NAME_ORDER + ".orderNo", AggregateOperator.COUNT_DISTINCT)));
|
||||
|
||||
new AggregateAction().execute(new AggregateInput(TestUtils.TABLE_NAME_LINE_ITEM)
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria(TestUtils.TABLE_NAME_ORDER + ".storeId", QCriteriaOperator.IS_NOT_BLANK)))
|
||||
.withGroupBy(new GroupBy(QFieldType.INTEGER, TestUtils.TABLE_NAME_ORDER + ".storeId")));
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// next 2 cases - explicitly state order table as a join //
|
||||
///////////////////////////////////////////////////////////
|
||||
new AggregateAction().execute(new AggregateInput(TestUtils.TABLE_NAME_LINE_ITEM)
|
||||
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER))
|
||||
.withAggregate(new Aggregate(TestUtils.TABLE_NAME_ORDER + ".orderNo", AggregateOperator.COUNT_DISTINCT)));
|
||||
|
||||
new AggregateAction().execute(new AggregateInput(TestUtils.TABLE_NAME_LINE_ITEM)
|
||||
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER))
|
||||
.withGroupBy(new GroupBy(QFieldType.INTEGER, TestUtils.TABLE_NAME_ORDER + ".storeId")));
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// now 2 fail cases, where join table can't be known, so fail //
|
||||
////////////////////////////////////////////////////////////////
|
||||
assertThatThrownBy(() -> new AggregateAction().execute(new AggregateInput(TestUtils.TABLE_NAME_LINE_ITEM)
|
||||
.withAggregate(new Aggregate(TestUtils.TABLE_NAME_ORDER + ".orderNo", AggregateOperator.COUNT_DISTINCT))))
|
||||
.hasMessageContaining("AggregateInput contained 1 unrecognized field name: order.orderNo");
|
||||
|
||||
assertThatThrownBy(() -> new AggregateAction().execute(new AggregateInput(TestUtils.TABLE_NAME_LINE_ITEM)
|
||||
.withGroupBy(new GroupBy(QFieldType.INTEGER, TestUtils.TABLE_NAME_ORDER + ".storeId"))))
|
||||
.hasMessageContaining("AggregateInput contained 1 unrecognized field name: order.storeId");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@@ -621,7 +621,7 @@ class QueryActionTest extends BaseTest
|
||||
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||
.withQueryJoin(new QueryJoin("noJoinTable").withSelect(true))
|
||||
.withFieldNamesToInclude(Set.of("noJoinTable.id"))))
|
||||
.hasMessageContaining("1 unrecognized field name: noJoinTable.id");
|
||||
.hasMessageContaining("Unrecognized name for join table: noJoinTable");
|
||||
|
||||
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||
.withFieldNamesToInclude(Set.of("noJoin.id"))))
|
||||
|
@@ -22,10 +22,13 @@
|
||||
package com.kingsrook.qqq.backend.module.rdbms.actions;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@@ -36,6 +39,7 @@ import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.strategy.BaseRDBMSActionStrategy;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -177,6 +181,15 @@ public class RDBMSInsertActionTest extends RDBMSActionTest
|
||||
));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// make sure ids got put on the inserted records in the output object //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
Set<Serializable> outputOrderLineIds = insertInput.getRecords().stream()
|
||||
.flatMap(r -> r.getAssociatedRecords().get("orderLine").stream())
|
||||
.map(r -> r.getValue("id"))
|
||||
.collect(Collectors.toSet());
|
||||
assertThat(outputOrderLineIds).allMatch(id -> id != null);
|
||||
|
||||
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
|
||||
assertEquals(originalNoOfOrders + 1, orders.size());
|
||||
assertTrue(orders.stream().anyMatch(r -> Objects.equals(r.getValue("billToPersonId"), 100) && Objects.equals(r.getValue("shipToPersonId"), 200)));
|
||||
|
Reference in New Issue
Block a user