diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateAction.java index 83012b0e..f8b51308 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateAction.java @@ -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 unrecognizedFieldNames = QueryAction.getUnrecognizedFieldNames(aggregateInput, inputFieldNames); + List 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))); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java index 5d8cb2b8..ef01720f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java @@ -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 unrecognizedFieldNames = getUnrecognizedFieldNames(queryInput, fieldNamesToInclude); + List unrecognizedFieldNames = SelectionValidationHelper.getUnrecognizedFieldNames(queryInput, fieldNamesToInclude); if(!unrecognizedFieldNames.isEmpty()) { @@ -205,91 +202,6 @@ public class QueryAction - /*************************************************************************** - * - ***************************************************************************/ - static List getUnrecognizedFieldNames(QueryOrCountInputInterface queryInput, Set fieldNames) throws QException - { - List unrecognizedFieldNames = new ArrayList<>(); - Map 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 diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/SelectionValidationHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/SelectionValidationHelper.java new file mode 100644 index 00000000..6f847ef7 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/SelectionValidationHelper.java @@ -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 . + */ + +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 getUnrecognizedFieldNames(QueryOrCountInputInterface input, Set fieldNames) throws QException + { + List unrecognizedFieldNames = new ArrayList<>(); + Map 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 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 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; + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java index 2265b7ef..175477aa 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java @@ -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)); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java index af4cf749..310352ad 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java @@ -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); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QSupplementalTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QSupplementalTableMetaData.java index b7c55723..2316867c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QSupplementalTableMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QSupplementalTableMetaData.java @@ -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> 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); + } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateActionTest.java index e9c94560..2ee33754 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/AggregateActionTest.java @@ -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"); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QueryActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QueryActionTest.java index 4c2be10d..8dc580dc 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QueryActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/QueryActionTest.java @@ -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")))) diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java index 0daa1381..24a11cc3 100644 --- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java +++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java @@ -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 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 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)));