Merge branch 'feature/CTLE-207-query-joins' into integration/sprint-25

# Conflicts:
#	qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QueryInput.java
#	qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java
This commit is contained in:
2023-05-02 07:52:15 -05:00
56 changed files with 2443 additions and 296 deletions

View File

@ -22,10 +22,12 @@
package com.kingsrook.qqq.backend.core.actions.reporting;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.BaseTest;
@ -36,16 +38,21 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
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.fields.QFieldMetaData;
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.utils.TestUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.FileUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -117,6 +124,68 @@ class ExportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testJoins() throws QException, IOException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
new QRecord().withValue("id", 1).withValue("orderNo", "ORD1").withValue("storeId", 1),
new QRecord().withValue("id", 2).withValue("orderNo", "ORD2").withValue("storeId", 1)
));
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM), List.of(
new QRecord().withValue("id", 1).withValue("orderId", 1).withValue("sku", "A").withValue("quantity", 10),
new QRecord().withValue("id", 2).withValue("orderId", 1).withValue("sku", "B").withValue("quantity", 15),
new QRecord().withValue("id", 3).withValue("orderId", 2).withValue("sku", "A").withValue("quantity", 20)
));
ExportInput exportInput = new ExportInput();
exportInput.setTableName(TestUtils.TABLE_NAME_ORDER);
exportInput.setReportFormat(ReportFormat.CSV);
ByteArrayOutputStream reportOutputStream = new ByteArrayOutputStream();
exportInput.setReportOutputStream(reportOutputStream);
exportInput.setQueryFilter(new QQueryFilter());
exportInput.setFieldNames(List.of("id", "orderNo", "storeId", "orderLine.id", "orderLine.sku", "orderLine.quantity"));
// exportInput.setFieldNames(List.of("id", "orderNo", "storeId"));
new ExportAction().execute(exportInput);
String csv = reportOutputStream.toString(StandardCharsets.UTF_8);
CSVParser parse = CSVParser.parse(csv, CSVFormat.DEFAULT.withFirstRecordAsHeader());
Iterator<CSVRecord> csvRecordIterator = parse.iterator();
assertFalse(parse.getHeaderMap().isEmpty());
assertTrue(parse.getHeaderMap().containsKey("Id"));
assertTrue(parse.getHeaderMap().containsKey("Order Line: Id"));
assertTrue(parse.getHeaderMap().containsKey("Order Line: SKU"));
CSVRecord csvRecord = csvRecordIterator.next();
assertEquals("1", csvRecord.get("Id"));
assertEquals("1", csvRecord.get("Order Line: Id"));
assertEquals("A", csvRecord.get("Order Line: SKU"));
assertEquals("10", csvRecord.get("Order Line: Quantity"));
csvRecord = csvRecordIterator.next();
assertEquals("1", csvRecord.get("Id"));
assertEquals("2", csvRecord.get("Order Line: Id"));
assertEquals("B", csvRecord.get("Order Line: SKU"));
assertEquals("15", csvRecord.get("Order Line: Quantity"));
csvRecord = csvRecordIterator.next();
assertEquals("2", csvRecord.get("Id"));
assertEquals("3", csvRecord.get("Order Line: Id"));
assertEquals("A", csvRecord.get("Order Line: SKU"));
assertEquals("20", csvRecord.get("Order Line: Quantity"));
assertFalse(csvRecordIterator.hasNext());
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -32,7 +32,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
@ -41,6 +45,7 @@ import org.junit.jupiter.api.Test;
import static com.kingsrook.qqq.backend.core.utils.TestUtils.APP_NAME_GREETINGS;
import static com.kingsrook.qqq.backend.core.utils.TestUtils.APP_NAME_MISCELLANEOUS;
import static com.kingsrook.qqq.backend.core.utils.TestUtils.APP_NAME_PEOPLE;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@ -304,4 +309,131 @@ class QInstanceEnricherTest extends BaseTest
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testExposedJoinPaths()
{
//////////////////////////
// no join path => fail //
//////////////////////////
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin().withJoinTable("B")));
qInstance.addTable(newTable("B", "id", "aId"));
assertThatThrownBy(() -> new QInstanceEnricher(qInstance).enrich())
.rootCause()
.hasMessageContaining("Could not infer a joinPath for table [A], exposedJoin to [B]")
.hasMessageContaining("No join connections between these tables exist in this instance.");
}
/////////////////////////////////
// multiple join paths => fail //
/////////////////////////////////
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin().withJoinTable("B")));
qInstance.addTable(newTable("B", "id", "aId1", "aId2"));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB1").withJoinOn(new JoinOn("id", "aId1")).withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB2").withJoinOn(new JoinOn("id", "aId2")).withType(JoinType.ONE_TO_ONE));
assertThatThrownBy(() -> new QInstanceEnricher(qInstance).enrich())
.rootCause()
.hasMessageContaining("Could not infer a joinPath for table [A], exposedJoin to [B]")
.hasMessageContaining("2 join connections exist")
.hasMessageContaining("\nAB1\n")
.hasMessageContaining("\nAB2.");
////////////////////////////////////////////
// but if you specify a path, you're good //
////////////////////////////////////////////
qInstance.getTable("A").getExposedJoins().get(0).setJoinPath(List.of("AB2"));
new QInstanceEnricher(qInstance).enrich();
assertEquals("B", qInstance.getTable("A").getExposedJoins().get(0).getLabel());
}
/////////////////////////////////
// multiple join paths => fail //
/////////////////////////////////
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin().withJoinTable("C")));
qInstance.addTable(newTable("B", "id", "aId"));
qInstance.addTable(newTable("C", "id", "bId", "aId"));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB").withJoinOn(new JoinOn("id", "aId")).withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withLeftTable("B").withRightTable("C").withName("BC").withJoinOn(new JoinOn("id", "bId")).withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("C").withName("AC").withJoinOn(new JoinOn("id", "aId")).withType(JoinType.ONE_TO_ONE));
assertThatThrownBy(() -> new QInstanceEnricher(qInstance).enrich())
.rootCause()
.hasMessageContaining("Could not infer a joinPath for table [A], exposedJoin to [C]")
.hasMessageContaining("2 join connections exist")
.hasMessageContaining("\nAB, BC\n")
.hasMessageContaining("\nAC.");
////////////////////////////////////////////
// but if you specify a path, you're good //
////////////////////////////////////////////
qInstance.getTable("A").getExposedJoins().get(0).setJoinPath(List.of("AB", "BC"));
new QInstanceEnricher(qInstance).enrich();
assertEquals("C", qInstance.getTable("A").getExposedJoins().get(0).getLabel());
}
//////////////////////////////////////////////////////////////////////////////////////
// even if you specify a bogus path, Enricher doesn't care - see validator to care. //
//////////////////////////////////////////////////////////////////////////////////////
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin().withJoinTable("B").withJoinPath(List.of("not-a-join"))));
qInstance.addTable(newTable("B", "id", "aId"));
new QInstanceEnricher(qInstance).enrich();
}
////////////////////////////////////
// one join path => great success //
////////////////////////////////////
{
QInstance qInstance = TestUtils.defineInstance();
qInstance.addTable(newTable("A", "id")
.withExposedJoin(new ExposedJoin().withJoinTable("B"))
.withExposedJoin(new ExposedJoin().withJoinTable("C")));
qInstance.addTable(newTable("B", "id", "aId"));
qInstance.addTable(newTable("C", "id", "bId"));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB").withJoinOn(new JoinOn("id", "aId")).withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withLeftTable("B").withRightTable("C").withName("BC").withJoinOn(new JoinOn("id", "bId")).withType(JoinType.ONE_TO_ONE));
new QInstanceEnricher(qInstance).enrich();
ExposedJoin exposedJoinAB = qInstance.getTable("A").getExposedJoins().get(0);
assertEquals("B", exposedJoinAB.getLabel());
assertEquals(List.of("AB"), exposedJoinAB.getJoinPath());
ExposedJoin exposedJoinAC = qInstance.getTable("A").getExposedJoins().get(1);
assertEquals("C", exposedJoinAC.getLabel());
assertEquals(List.of("AB", "BC"), exposedJoinAC.getJoinPath());
}
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData newTable(String tableName, String... fieldNames)
{
QTableMetaData tableMetaData = new QTableMetaData()
.withName(tableName)
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withPrimaryKeyField(fieldNames[0]);
for(String fieldName : fieldNames)
{
tableMetaData.addField(new QFieldMetaData(fieldName, QFieldType.INTEGER));
}
return (tableMetaData);
}
}

View File

@ -50,6 +50,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
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.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
@ -65,6 +68,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
@ -1671,6 +1675,83 @@ class QInstanceValidatorTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testExposedJoinPaths()
{
assertValidationFailureReasons(qInstance -> qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin())),
"Table A has an exposedJoin that is missing a joinTable name",
"Table A exposedJoin [missingJoinTableName] is missing a label");
assertValidationFailureReasons(qInstance -> qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin().withJoinTable("B"))),
"Table A exposedJoin B is referencing an unrecognized table",
"Table A exposedJoin B is missing a label");
assertValidationFailureReasons(qInstance ->
{
qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin().withJoinTable("B").withLabel("B").withJoinPath(List.of("notAJoin"))));
qInstance.addTable(newTable("B", "id", "aId"));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB").withType(JoinType.ONE_TO_ONE).withJoinOn(new JoinOn("id", "aId")));
},
"does not match a valid join connection in the instance");
assertValidationFailureReasons(qInstance ->
{
qInstance.addTable(newTable("A", "id")
.withExposedJoin(new ExposedJoin().withJoinTable("B").withLabel("foo").withJoinPath(List.of("AB")))
.withExposedJoin(new ExposedJoin().withJoinTable("C").withLabel("foo").withJoinPath(List.of("AC")))
);
qInstance.addTable(newTable("B", "id", "aId"));
qInstance.addTable(newTable("C", "id", "aId"));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB").withType(JoinType.ONE_TO_ONE).withJoinOn(new JoinOn("id", "aId")));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("C").withName("AC").withType(JoinType.ONE_TO_ONE).withJoinOn(new JoinOn("id", "aId")));
},
"more than one join labeled: foo");
assertValidationFailureReasons(qInstance ->
{
qInstance.addTable(newTable("A", "id")
.withExposedJoin(new ExposedJoin().withJoinTable("B").withLabel("B1").withJoinPath(List.of("AB")))
.withExposedJoin(new ExposedJoin().withJoinTable("B").withLabel("B2").withJoinPath(List.of("AB")))
);
qInstance.addTable(newTable("B", "id", "aId"));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB").withType(JoinType.ONE_TO_ONE).withJoinOn(new JoinOn("id", "aId")));
},
"than one join with the joinPath: [AB]");
assertValidationSuccess(qInstance ->
{
qInstance.addTable(newTable("A", "id").withExposedJoin(new ExposedJoin().withJoinTable("B").withLabel("B").withJoinPath(List.of("AB"))));
qInstance.addTable(newTable("B", "id", "aId"));
qInstance.addJoin(new QJoinMetaData().withLeftTable("A").withRightTable("B").withName("AB").withType(JoinType.ONE_TO_ONE).withJoinOn(new JoinOn("id", "aId")));
});
}
/*******************************************************************************
**
*******************************************************************************/
private QTableMetaData newTable(String tableName, String... fieldNames)
{
QTableMetaData tableMetaData = new QTableMetaData()
.withName(tableName)
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withPrimaryKeyField(fieldNames[0]);
for(String fieldName : fieldNames)
{
tableMetaData.addField(new QFieldMetaData(fieldName, QFieldType.INTEGER));
}
return (tableMetaData);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,137 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.model.metadata.tables;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for ExposedJoin
*******************************************************************************/
class ExposedJoinTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIsManyOneToOne()
{
QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData().withName("A").withExposedJoin(new ExposedJoin().withJoinTable("B").withJoinPath(List.of("AB"))));
qInstance.addTable(new QTableMetaData().withName("B").withExposedJoin(new ExposedJoin().withJoinTable("A").withJoinPath(List.of("AB"))));
qInstance.addJoin(new QJoinMetaData().withName("AB").withLeftTable("A").withRightTable("B").withType(JoinType.ONE_TO_ONE));
assertFalse(qInstance.getTable("A").getExposedJoins().get(0).getIsMany());
assertFalse(qInstance.getTable("B").getExposedJoins().get(0).getIsMany());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIsManyOneToMany()
{
QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData().withName("A").withExposedJoin(new ExposedJoin().withJoinTable("B").withJoinPath(List.of("AB"))));
qInstance.addTable(new QTableMetaData().withName("B").withExposedJoin(new ExposedJoin().withJoinTable("A").withJoinPath(List.of("AB"))));
qInstance.addJoin(new QJoinMetaData().withName("AB").withLeftTable("A").withRightTable("B").withType(JoinType.ONE_TO_MANY));
assertTrue(qInstance.getTable("A").getExposedJoins().get(0).getIsMany());
assertFalse(qInstance.getTable("B").getExposedJoins().get(0).getIsMany());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIsManyManyToOne()
{
QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData().withName("A").withExposedJoin(new ExposedJoin().withJoinTable("B").withJoinPath(List.of("AB"))));
qInstance.addTable(new QTableMetaData().withName("B").withExposedJoin(new ExposedJoin().withJoinTable("A").withJoinPath(List.of("AB"))));
qInstance.addJoin(new QJoinMetaData().withName("AB").withLeftTable("A").withRightTable("B").withType(JoinType.MANY_TO_ONE));
assertFalse(qInstance.getTable("A").getExposedJoins().get(0).getIsMany());
assertTrue(qInstance.getTable("B").getExposedJoins().get(0).getIsMany());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIsManyOneToOneThroughChain()
{
QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData().withName("A").withExposedJoin(new ExposedJoin().withJoinTable("G").withJoinPath(List.of("AB", "BC", "CD", "DE", "EF", "FG"))));
qInstance.addTable(new QTableMetaData().withName("B").withExposedJoin(new ExposedJoin().withJoinTable("G").withJoinPath(List.of("BC", "CD", "DE", "EF", "FG"))));
qInstance.addJoin(new QJoinMetaData().withName("AB").withLeftTable("A").withRightTable("B").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("BC").withLeftTable("B").withRightTable("C").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("CD").withLeftTable("C").withRightTable("D").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("DE").withLeftTable("D").withRightTable("E").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("EF").withLeftTable("E").withRightTable("F").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("FG").withLeftTable("F").withRightTable("G").withType(JoinType.ONE_TO_ONE));
assertFalse(qInstance.getTable("A").getExposedJoins().get(0).getIsMany());
assertFalse(qInstance.getTable("B").getExposedJoins().get(0).getIsMany());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIsManyOneToManyThroughChain()
{
QInstance qInstance = QContext.getQInstance();
qInstance.addTable(new QTableMetaData().withName("A").withExposedJoin(new ExposedJoin().withJoinTable("G").withJoinPath(List.of("AB", "BC", "CD", "DE", "EF", "FG"))));
qInstance.addTable(new QTableMetaData().withName("B").withExposedJoin(new ExposedJoin().withJoinTable("E").withJoinPath(List.of("BC", "CD", "DE"))));
qInstance.addTable(new QTableMetaData().withName("F").withExposedJoin(new ExposedJoin().withJoinTable("C").withJoinPath(List.of("FG", "EF", "DE", "CD"))));
qInstance.addJoin(new QJoinMetaData().withName("AB").withLeftTable("A").withRightTable("B").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("BC").withLeftTable("B").withRightTable("C").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("CD").withLeftTable("C").withRightTable("D").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("DE").withLeftTable("D").withRightTable("E").withType(JoinType.ONE_TO_ONE));
qInstance.addJoin(new QJoinMetaData().withName("EF").withLeftTable("E").withRightTable("F").withType(JoinType.ONE_TO_MANY));
qInstance.addJoin(new QJoinMetaData().withName("FG").withLeftTable("F").withRightTable("G").withType(JoinType.ONE_TO_ONE));
assertTrue(qInstance.getTable("A").getExposedJoins().get(0).getIsMany());
assertFalse(qInstance.getTable("B").getExposedJoins().get(0).getIsMany());
assertFalse(qInstance.getTable("F").getExposedJoins().get(0).getIsMany());
}
}

View File

@ -128,38 +128,31 @@ class EnumerationQueryActionTest extends BaseTest
QueryInput queryInput = new QueryInput();
queryInput.setTableName("statesEnum");
queryInput.setSkip(0);
queryInput.setLimit(null);
queryInput.setFilter(new QQueryFilter().withSkip(0).withLimit(null));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
assertEquals(List.of("Missouri", "Illinois"), queryOutput.getRecords().stream().map(r -> r.getValueString("name")).toList());
queryInput.setSkip(1);
queryInput.setLimit(null);
queryInput.setFilter(new QQueryFilter().withSkip(1).withLimit(null));
queryOutput = new QueryAction().execute(queryInput);
assertEquals(List.of("Illinois"), queryOutput.getRecords().stream().map(r -> r.getValueString("name")).toList());
queryInput.setSkip(2);
queryInput.setLimit(null);
queryInput.setFilter(new QQueryFilter().withSkip(2).withLimit(null));
queryOutput = new QueryAction().execute(queryInput);
assertEquals(List.of(), queryOutput.getRecords().stream().map(r -> r.getValueString("name")).toList());
queryInput.setSkip(null);
queryInput.setLimit(1);
queryInput.setFilter(new QQueryFilter().withSkip(null).withLimit(1));
queryOutput = new QueryAction().execute(queryInput);
assertEquals(List.of("Missouri"), queryOutput.getRecords().stream().map(r -> r.getValueString("name")).toList());
queryInput.setSkip(null);
queryInput.setLimit(2);
queryInput.setFilter(new QQueryFilter().withSkip(null).withLimit(2));
queryOutput = new QueryAction().execute(queryInput);
assertEquals(List.of("Missouri", "Illinois"), queryOutput.getRecords().stream().map(r -> r.getValueString("name")).toList());
queryInput.setSkip(null);
queryInput.setLimit(3);
queryInput.setFilter(new QQueryFilter().withSkip(null).withLimit(3));
queryOutput = new QueryAction().execute(queryInput);
assertEquals(List.of("Missouri", "Illinois"), queryOutput.getRecords().stream().map(r -> r.getValueString("name")).toList());
queryInput.setSkip(null);
queryInput.setLimit(0);
queryInput.setFilter(new QQueryFilter().withSkip(null).withLimit(0));
queryOutput = new QueryAction().execute(queryInput);
assertEquals(List.of(), queryOutput.getRecords().stream().map(r -> r.getValueString("name")).toList());
}

View File

@ -341,14 +341,13 @@ class MemoryBackendModuleTest extends BaseTest
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(table.getName());
queryInput.setLimit(2);
queryInput.setFilter(new QQueryFilter().withLimit(2));
assertEquals(2, new QueryAction().execute(queryInput).getRecords().size());
queryInput.setLimit(1);
queryInput.setFilter(new QQueryFilter().withLimit(1));
assertEquals(1, new QueryAction().execute(queryInput).getRecords().size());
queryInput.setSkip(4);
queryInput.setLimit(3);
queryInput.setFilter(new QQueryFilter().withSkip(4).withLimit(3));
assertEquals(0, new QueryAction().execute(queryInput).getRecords().size());
}

View File

@ -92,6 +92,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking;
@ -548,6 +549,7 @@ public class TestUtils
.withFieldName("storeId"))
.withAssociation(new Association().withName("orderLine").withAssociatedTableName(TABLE_NAME_LINE_ITEM).withJoinName("orderLineItem"))
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_ORDER_EXTRINSIC).withJoinName("orderOrderExtrinsic"))
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_LINE_ITEM))
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
@ -582,7 +584,7 @@ public class TestUtils
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER))
.withField(new QFieldMetaData("lineNumber", QFieldType.STRING))
.withField(new QFieldMetaData("sku", QFieldType.STRING))
.withField(new QFieldMetaData("sku", QFieldType.STRING).withLabel("SKU"))
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER));
}

View File

@ -22,9 +22,11 @@
package com.kingsrook.qqq.backend.core.utils.collections;
import java.util.LinkedList;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
@ -48,4 +50,22 @@ class MutableListTest extends BaseTest
list.remove(0);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNullInput()
{
List<Integer> list = new MutableList<>(null);
list.add(1);
assertEquals(1, list.size());
MutableList<Integer> mutableList = new MutableList<>(null, LinkedList::new);
mutableList.add(1);
assertEquals(1, mutableList.size());
assertEquals(LinkedList.class, mutableList.getUnderlyingList().getClass());
}
}

View File

@ -22,9 +22,11 @@
package com.kingsrook.qqq.backend.core.utils.collections;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
@ -48,4 +50,22 @@ class MutableMapTest extends BaseTest
map.putAll(Map.of("c", 3, "d", 4));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNullInput()
{
Map<Integer, String> map = new MutableMap<>(null);
map.put(1, "one");
assertEquals(1, map.size());
MutableMap<Integer, String> mutableMap = new MutableMap<>(null, LinkedHashMap::new);
mutableMap.put(1, "uno");
assertEquals(1, mutableMap.size());
assertEquals(LinkedHashMap.class, mutableMap.getUnderlyingMap().getClass());
}
}