Better testing on join reports, possible value translations; renamed left & right in QueryJoin (now joinTable, baseTable)

This commit is contained in:
2022-12-22 13:39:37 -06:00
parent 799b695e14
commit 428f48602b
19 changed files with 851 additions and 120 deletions

View File

@ -183,38 +183,48 @@ public abstract class AbstractRDBMSAction implements QActionInterface
for(QueryJoin queryJoin : joinsContext.getQueryJoins())
{
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
rs.append(" ").append(queryJoin.getType()).append(" JOIN ")
.append(escapeIdentifier(getTableName(joinTable)))
.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<>();
String leftTableName = joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getLeftTableOrAlias());
String baseTableName = joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getBaseTableOrAlias());
QJoinMetaData joinMetaData = Objects.requireNonNullElseGet(queryJoin.getJoinMetaData(), () ->
{
QJoinMetaData found = findJoinMetaData(instance, leftTableName, queryJoin.getRightTable());
QJoinMetaData found = findJoinMetaData(instance, joinsContext, baseTableName, queryJoin.getJoinTable());
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);
});
for(JoinOn joinOn : joinMetaData.getJoinOns())
{
QTableMetaData leftTable = instance.getTable(joinMetaData.getLeftTable());
QTableMetaData rightTable = instance.getTable(joinMetaData.getRightTable());
String leftTableOrAlias = queryJoin.getLeftTableOrAlias();
String aliasOrRightTable = queryJoin.getAliasOrRightTable();
String baseTableOrAlias = queryJoin.getBaseTableOrAlias();
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(aliasOrRightTable)
+ " = " + escapeIdentifier(joinTableOrAlias)
+ "." + escapeIdentifier(getColumnName((rightTable.getField(joinOn.getRightField())))));
}
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<>();
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! //
//////////////////////////////
if(join.getRightTable().equals(leftTable) && join.getLeftTable().equals(rightTable))
//////////////////////////////
// look in both directions! //
//////////////////////////////
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)
{
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);

View File

@ -121,8 +121,8 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
{
if(queryJoin.getSelect())
{
QTableMetaData joinTable = queryInput.getInstance().getTable(queryJoin.getRightTable());
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
QTableMetaData joinTable = queryInput.getInstance().getTable(queryJoin.getJoinTable());
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
for(QFieldMetaData joinField : joinTable.getFields().values())
{
fieldList.add(joinField.clone().withName(tableNameOrAlias + "." + joinField.getName()));
@ -201,11 +201,11 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
{
if(queryJoin.getSelect())
{
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
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());

View File

@ -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.JoinType;
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.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
@ -90,6 +93,7 @@ public class TestUtils
QInstance qInstance = new QInstance();
qInstance.addBackend(defineBackend());
qInstance.addTable(defineTablePerson());
qInstance.addPossibleValueSource(definePvsPerson());
qInstance.addTable(defineTablePersonalIdCard());
qInstance.addJoin(defineJoinPersonAndPersonalIdCard());
addOmsTablesAndJoins(qInstance);
@ -135,6 +139,8 @@ public class TestUtils
return new QTableMetaData()
.withName(TABLE_NAME_PERSON)
.withLabel("Person")
.withRecordLabelFormat("%s %s")
.withRecordLabelFields("firstName", "lastName")
.withBackendName(DEFAULT_BACKEND_NAME)
.withPrimaryKeyField("id")
.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.
**
@ -196,24 +217,26 @@ public class TestUtils
private static void addOmsTablesAndJoins(QInstance qInstance)
{
qInstance.addTable(defineBaseTable(TABLE_NAME_STORE, "store")
.withRecordLabelFormat("%s")
.withRecordLabelFields("name")
.withField(new QFieldMetaData("name", QFieldType.STRING))
);
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER, "order")
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id"))
.withField(new QFieldMetaData("billToPersonId", QFieldType.INTEGER).withBackendName("bill_to_person_id"))
.withField(new QFieldMetaData("shipToPersonId", QFieldType.INTEGER).withBackendName("ship_to_person_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").withPossibleValueSourceName(TABLE_NAME_PERSON))
.withField(new QFieldMetaData("shipToPersonId", QFieldType.INTEGER).withBackendName("ship_to_person_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
);
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
.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")
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
.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))
);
@ -266,6 +289,12 @@ public class TestUtils
.withJoinOn(new JoinOn("storeId", "storeId"))
);
qInstance.addPossibleValueSource(new QPossibleValueSource()
.withName("store")
.withType(QPossibleValueSourceType.TABLE)
.withTableName(TABLE_NAME_STORE)
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
);
}

View File

@ -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.metadata.QInstance;
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 org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@ -611,7 +612,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
void testOneToOneInnerJoinWithoutWhere() throws QException
{
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);
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"));
@ -628,7 +629,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
void testOneToOneLeftJoinWithoutWhere() throws QException
{
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);
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"));
@ -647,7 +648,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
void testOneToOneRightJoinWithoutWhere() throws QException
{
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);
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"));
@ -667,7 +668,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
void testOneToOneInnerJoinWithWhere() throws QException
{
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")));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
assertEquals(2, queryOutput.getRecords().size(), "Join query should find 2 rows");
@ -685,7 +686,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
{
QInstance qInstance = TestUtils.defineInstance();
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")));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
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.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);
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 //
/////////////////////////////////////////////////////
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);
assertEquals(6, queryOutput.getRecords().size(), "# of rows found by query");
@ -829,6 +830,42 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
queryOutput = new QueryAction().execute(queryInput);
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"));
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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]");
}

View File

@ -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());
}
}