mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Much implementation of joins for RDBMS
This commit is contained in:
@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutp
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
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.reporting.ReportInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
@ -185,7 +186,7 @@ public class GenerateReportAction
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void startTableView(ReportInput reportInput, QReportDataSource dataSource, QReportView reportView) throws QReportingException
|
private void startTableView(ReportInput reportInput, QReportDataSource dataSource, QReportView reportView) throws QException
|
||||||
{
|
{
|
||||||
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||||
|
|
||||||
@ -200,6 +201,8 @@ public class GenerateReportAction
|
|||||||
exportInput.setIncludeHeaderRow(reportView.getIncludeHeaderRow());
|
exportInput.setIncludeHeaderRow(reportView.getIncludeHeaderRow());
|
||||||
exportInput.setReportOutputStream(reportInput.getReportOutputStream());
|
exportInput.setReportOutputStream(reportInput.getReportOutputStream());
|
||||||
|
|
||||||
|
JoinsContext joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins());
|
||||||
|
|
||||||
List<QFieldMetaData> fields;
|
List<QFieldMetaData> fields;
|
||||||
if(CollectionUtils.nullSafeHasContents(reportView.getColumns()))
|
if(CollectionUtils.nullSafeHasContents(reportView.getColumns()))
|
||||||
{
|
{
|
||||||
@ -212,7 +215,14 @@ public class GenerateReportAction
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QFieldMetaData field = table.getField(column.getName()).clone();
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(column.getName());
|
||||||
|
if(fieldAndTableNameOrAlias.field() == null)
|
||||||
|
{
|
||||||
|
throw new QReportingException("Could not find field named [" + column.getName() + "] on table [" + table.getName() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
QFieldMetaData field = fieldAndTableNameOrAlias.field().clone();
|
||||||
|
field.setName(column.getName());
|
||||||
if(StringUtils.hasContent(column.getLabel()))
|
if(StringUtils.hasContent(column.getLabel()))
|
||||||
{
|
{
|
||||||
field.setLabel(column.getLabel());
|
field.setLabel(column.getLabel());
|
||||||
@ -278,6 +288,7 @@ public class GenerateReportAction
|
|||||||
queryInput.setRecordPipe(recordPipe);
|
queryInput.setRecordPipe(recordPipe);
|
||||||
queryInput.setTableName(dataSource.getSourceTable());
|
queryInput.setTableName(dataSource.getSourceTable());
|
||||||
queryInput.setFilter(queryFilter);
|
queryInput.setFilter(queryFilter);
|
||||||
|
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||||
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
||||||
return (new QueryAction().execute(queryInput));
|
return (new QueryAction().execute(queryInput));
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponen
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
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.tables.QFieldSection;
|
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.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||||
@ -365,6 +367,50 @@ public class QInstanceEnricher
|
|||||||
{
|
{
|
||||||
report.getInputFields().forEach(this::enrichField);
|
report.getInputFields().forEach(this::enrichField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if there's only 1 data source in the report, and it doesn't have a name, give it a default name //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
String singleDataSourceName = null;
|
||||||
|
if(report.getDataSources() != null)
|
||||||
|
{
|
||||||
|
if(report.getDataSources().size() == 1)
|
||||||
|
{
|
||||||
|
QReportDataSource dataSource = report.getDataSources().get(0);
|
||||||
|
if(!StringUtils.hasContent(dataSource.getName()))
|
||||||
|
{
|
||||||
|
dataSource.setName("DEFAULT");
|
||||||
|
}
|
||||||
|
singleDataSourceName = dataSource.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(report.getViews() != null)
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if there's only 1 view in the report, and it doesn't have a name, give it a default name //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(report.getViews().size() == 1)
|
||||||
|
{
|
||||||
|
QReportView view = report.getViews().get(0);
|
||||||
|
if(!StringUtils.hasContent(view.getName()))
|
||||||
|
{
|
||||||
|
view.setName("DEFAULT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// for any views in the report, if they don't specify a data source name, //
|
||||||
|
// but there's only 1 data source, then use that single data source's name //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
for(QReportView view : report.getViews())
|
||||||
|
{
|
||||||
|
if(!StringUtils.hasContent(view.getDataSourceName()) && singleDataSourceName != null)
|
||||||
|
{
|
||||||
|
view.setDataSourceName(singleDataSourceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ public class AggregateInput extends AbstractTableActionInput
|
|||||||
private List<Aggregate> aggregates;
|
private List<Aggregate> aggregates;
|
||||||
private List<String> groupByFieldNames;
|
private List<String> groupByFieldNames;
|
||||||
|
|
||||||
|
private List<QueryJoin> queryJoins = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -192,4 +195,54 @@ public class AggregateInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<QueryJoin> getQueryJoins()
|
||||||
|
{
|
||||||
|
return queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public AggregateInput withQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public AggregateInput withQueryJoin(QueryJoin queryJoin)
|
||||||
|
{
|
||||||
|
if(this.queryJoins == null)
|
||||||
|
{
|
||||||
|
this.queryJoins = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.queryJoins.add(queryJoin);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,11 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.actions.tables.count;
|
package com.kingsrook.qqq.backend.core.model.actions.tables.count;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +38,8 @@ public class CountInput extends AbstractTableActionInput
|
|||||||
{
|
{
|
||||||
private QQueryFilter filter;
|
private QQueryFilter filter;
|
||||||
|
|
||||||
|
private List<QueryJoin> queryJoins = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -78,4 +83,52 @@ public class CountInput extends AbstractTableActionInput
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<QueryJoin> getQueryJoins()
|
||||||
|
{
|
||||||
|
return queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public CountInput withQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public CountInput withQueryJoin(QueryJoin queryJoin)
|
||||||
|
{
|
||||||
|
if(this.queryJoins == null)
|
||||||
|
{
|
||||||
|
this.queryJoins = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.queryJoins.add(queryJoin);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.model.actions.tables.query;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
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.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Helper object used throughout query (and related (count, aggregate, reporting))
|
||||||
|
** actions that need to track joins and aliases.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class JoinsContext
|
||||||
|
{
|
||||||
|
private final QInstance instance;
|
||||||
|
private final String mainTableName;
|
||||||
|
private final List<QueryJoin> queryJoins;
|
||||||
|
private final Map<String, String> aliasToTableNameMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public JoinsContext(QInstance instance, String tableName, List<QueryJoin> queryJoins) throws QException
|
||||||
|
{
|
||||||
|
this.instance = instance;
|
||||||
|
this.mainTableName = tableName;
|
||||||
|
this.queryJoins = CollectionUtils.nonNullList(queryJoins);
|
||||||
|
|
||||||
|
for(QueryJoin queryJoin : this.queryJoins)
|
||||||
|
{
|
||||||
|
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
|
||||||
|
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
||||||
|
if(aliasToTableNameMap.containsKey(tableNameOrAlias))
|
||||||
|
{
|
||||||
|
throw (new QException("Duplicate table name or alias: " + tableNameOrAlias));
|
||||||
|
}
|
||||||
|
aliasToTableNameMap.put(tableNameOrAlias, joinTable.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<QueryJoin> getQueryJoins()
|
||||||
|
{
|
||||||
|
return queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String resolveTableNameOrAliasToTableName(String nameOrAlias)
|
||||||
|
{
|
||||||
|
if(aliasToTableNameMap.containsKey(nameOrAlias))
|
||||||
|
{
|
||||||
|
return (aliasToTableNameMap.get(nameOrAlias));
|
||||||
|
}
|
||||||
|
return (nameOrAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public FieldAndTableNameOrAlias getFieldAndTableNameOrAlias(String fieldName)
|
||||||
|
{
|
||||||
|
if(fieldName.contains("."))
|
||||||
|
{
|
||||||
|
String[] parts = fieldName.split("\\.");
|
||||||
|
if(parts.length != 2)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Mal-formatted field name in query: " + fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String tableOrAlias = parts[0];
|
||||||
|
String baseFieldName = parts[1];
|
||||||
|
String tableName = resolveTableNameOrAliasToTableName(tableOrAlias);
|
||||||
|
|
||||||
|
QTableMetaData table = instance.getTable(tableName);
|
||||||
|
if(table == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Could not find table [" + tableName + "] in instance for query");
|
||||||
|
}
|
||||||
|
return new FieldAndTableNameOrAlias(table.getField(baseFieldName), tableOrAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FieldAndTableNameOrAlias(instance.getTable(mainTableName).getField(fieldName), mainTableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public record FieldAndTableNameOrAlias(QFieldMetaData field, String tableNameOrAlias)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@ -43,6 +44,8 @@ public class QFilterCriteria implements Serializable, Cloneable
|
|||||||
private QCriteriaOperator operator;
|
private QCriteriaOperator operator;
|
||||||
private List<Serializable> values;
|
private List<Serializable> values;
|
||||||
|
|
||||||
|
private String otherFieldName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -220,6 +223,40 @@ public class QFilterCriteria implements Serializable, Cloneable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for otherFieldName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getOtherFieldName()
|
||||||
|
{
|
||||||
|
return otherFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for otherFieldName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOtherFieldName(String otherFieldName)
|
||||||
|
{
|
||||||
|
this.otherFieldName = otherFieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for otherFieldName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QFilterCriteria withOtherFieldName(String otherFieldName)
|
||||||
|
{
|
||||||
|
this.otherFieldName = otherFieldName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -232,21 +269,28 @@ public class QFilterCriteria implements Serializable, Cloneable
|
|||||||
rs.append(" ").append(operator).append(" ");
|
rs.append(" ").append(operator).append(" ");
|
||||||
if(CollectionUtils.nullSafeHasContents(values))
|
if(CollectionUtils.nullSafeHasContents(values))
|
||||||
{
|
{
|
||||||
if(values.size() == 1)
|
if(StringUtils.hasContent(otherFieldName))
|
||||||
{
|
{
|
||||||
rs.append(values.get(0));
|
rs.append(otherFieldName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int index = 0;
|
if(values.size() == 1)
|
||||||
for(Serializable value : values)
|
|
||||||
{
|
{
|
||||||
if(index++ > 9)
|
rs.append(values.get(0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
for(Serializable value : values)
|
||||||
{
|
{
|
||||||
rs.append("and ").append(values.size() - index).append(" more");
|
if(index++ > 9)
|
||||||
break;
|
{
|
||||||
|
rs.append("and ").append(values.size() - index).append(" more");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rs.append(value).append(",");
|
||||||
}
|
}
|
||||||
rs.append(value).append(",");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||||
@ -45,6 +47,8 @@ public class QueryInput extends AbstractTableActionInput
|
|||||||
private boolean shouldTranslatePossibleValues = false;
|
private boolean shouldTranslatePossibleValues = false;
|
||||||
private boolean shouldGenerateDisplayValues = false;
|
private boolean shouldGenerateDisplayValues = false;
|
||||||
|
|
||||||
|
private List<QueryJoin> queryJoins = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -241,4 +245,54 @@ public class QueryInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<QueryJoin> getQueryJoins()
|
||||||
|
{
|
||||||
|
return queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryInput withQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryInput withQueryJoin(QueryJoin queryJoin)
|
||||||
|
{
|
||||||
|
if(this.queryJoins == null)
|
||||||
|
{
|
||||||
|
this.queryJoins = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.queryJoins.add(queryJoin);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.model.actions.tables.query;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Part of query (or count, aggregate) input, to do a Join as part of a query.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QueryJoin
|
||||||
|
{
|
||||||
|
private String leftTableOrAlias;
|
||||||
|
private String rightTable;
|
||||||
|
private QJoinMetaData joinMetaData;
|
||||||
|
private String alias;
|
||||||
|
private boolean select = false;
|
||||||
|
private Type type = Type.INNER;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum Type
|
||||||
|
{INNER, LEFT, RIGHT, FULL}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin(String leftTableOrAlias, String rightTable)
|
||||||
|
{
|
||||||
|
this.leftTableOrAlias = leftTableOrAlias;
|
||||||
|
this.rightTable = rightTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin(QJoinMetaData joinMetaData)
|
||||||
|
{
|
||||||
|
setJoinMetaData(joinMetaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for leftTableOrAlias
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLeftTableOrAlias()
|
||||||
|
{
|
||||||
|
return leftTableOrAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for leftTableOrAlias
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setLeftTableOrAlias(String leftTableOrAlias)
|
||||||
|
{
|
||||||
|
this.leftTableOrAlias = leftTableOrAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for leftTableOrAlias
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin withLeftTableOrAlias(String leftTableOrAlias)
|
||||||
|
{
|
||||||
|
this.leftTableOrAlias = leftTableOrAlias;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for rightTable
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getRightTable()
|
||||||
|
{
|
||||||
|
return rightTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for rightTable
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRightTable(String rightTable)
|
||||||
|
{
|
||||||
|
this.rightTable = rightTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for rightTable
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin withRightTable(String rightTable)
|
||||||
|
{
|
||||||
|
this.rightTable = rightTable;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for alias
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getAlias()
|
||||||
|
{
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for alias
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setAlias(String alias)
|
||||||
|
{
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for alias
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin withAlias(String alias)
|
||||||
|
{
|
||||||
|
this.alias = alias;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for select
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getSelect()
|
||||||
|
{
|
||||||
|
return select;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for select
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSelect(boolean select)
|
||||||
|
{
|
||||||
|
this.select = select;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for select
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin withSelect(boolean select)
|
||||||
|
{
|
||||||
|
this.select = select;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getAliasOrRightTable()
|
||||||
|
{
|
||||||
|
if(StringUtils.hasContent(alias))
|
||||||
|
{
|
||||||
|
return (alias);
|
||||||
|
}
|
||||||
|
return (rightTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for type
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Type getType()
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for type
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setType(Type type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for type
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin withType(Type type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for joinMetaData
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QJoinMetaData getJoinMetaData()
|
||||||
|
{
|
||||||
|
return joinMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for joinMetaData
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setJoinMetaData(QJoinMetaData joinMetaData)
|
||||||
|
{
|
||||||
|
this.joinMetaData = joinMetaData;
|
||||||
|
|
||||||
|
if(!StringUtils.hasContent(this.leftTableOrAlias) && !StringUtils.hasContent(this.rightTable))
|
||||||
|
{
|
||||||
|
setLeftTableOrAlias(joinMetaData.getLeftTable());
|
||||||
|
setRightTable(joinMetaData.getRightTable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for joinMetaData
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryJoin withJoinMetaData(QJoinMetaData joinMetaData)
|
||||||
|
{
|
||||||
|
setJoinMetaData(joinMetaData);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,7 +23,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.joins;
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** Specification for (at least part of) how two tables join together - e.g.,
|
||||||
|
** leftField = rightField. Used as part of a list in a QJoinMetaData.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class JoinOn
|
public class JoinOn
|
||||||
{
|
{
|
||||||
@ -54,6 +55,16 @@ public class JoinOn
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Return a new JoinOn, with the fields of this one, but flipped (right ←→ left)
|
||||||
|
*******************************************************************************/
|
||||||
|
public JoinOn flip()
|
||||||
|
{
|
||||||
|
return new JoinOn(rightField, leftField);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for leftField
|
** Getter for leftField
|
||||||
**
|
**
|
||||||
|
@ -25,7 +25,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.joins;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Type for a QJoin.
|
** Type for a QJoin.
|
||||||
**
|
**
|
||||||
** - One to One - what about zero??
|
** - One to One - or zero, i guess...
|
||||||
** - One to Many - e.g., where the parent record really "owns" all of the child
|
** - One to Many - e.g., where the parent record really "owns" all of the child
|
||||||
** records. Like Order -> OrderLine.
|
** records. Like Order -> OrderLine.
|
||||||
** - Many to One - e.g., where a child references a parent, but we'd never really
|
** - Many to One - e.g., where a child references a parent, but we'd never really
|
||||||
@ -37,5 +37,21 @@ public enum JoinType
|
|||||||
ONE_TO_ONE,
|
ONE_TO_ONE,
|
||||||
ONE_TO_MANY,
|
ONE_TO_MANY,
|
||||||
MANY_TO_ONE,
|
MANY_TO_ONE,
|
||||||
MANY_TO_MANY
|
MANY_TO_MANY;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("checkstyle:indentation")
|
||||||
|
public JoinType flip()
|
||||||
|
{
|
||||||
|
return switch(this)
|
||||||
|
{
|
||||||
|
case ONE_TO_MANY -> MANY_TO_ONE;
|
||||||
|
case MANY_TO_ONE -> ONE_TO_MANY;
|
||||||
|
case MANY_TO_MANY, ONE_TO_ONE -> this;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** Definition of how 2 tables join together within a QQQ Instance.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QJoinMetaData
|
public class QJoinMetaData
|
||||||
{
|
{
|
||||||
@ -43,6 +43,22 @@ public class QJoinMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Return a new QJoinMetaData, with all the same data as this one, but flipped
|
||||||
|
** right ←→ left, for the tables, the type, and the joinOns.
|
||||||
|
*******************************************************************************/
|
||||||
|
public QJoinMetaData flip()
|
||||||
|
{
|
||||||
|
return (new QJoinMetaData()
|
||||||
|
.withLeftTable(rightTable)
|
||||||
|
.withRightTable(leftTable)
|
||||||
|
.withType(type.flip())
|
||||||
|
.withJoinOns(joinOns.stream().map(JoinOn::flip).toList()));
|
||||||
|
// todo - what about order bys??
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for name
|
** Getter for name
|
||||||
**
|
**
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
|
||||||
|
|
||||||
@ -32,9 +35,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QReportDataSource
|
public class QReportDataSource
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private String sourceTable;
|
|
||||||
private QQueryFilter queryFilter;
|
private String sourceTable;
|
||||||
|
private QQueryFilter queryFilter;
|
||||||
|
private List<QueryJoin> queryJoins = null;
|
||||||
|
|
||||||
|
|
||||||
private QCodeReference staticDataSupplier;
|
private QCodeReference staticDataSupplier;
|
||||||
|
|
||||||
@ -174,4 +180,54 @@ public class QReportDataSource
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<QueryJoin> getQueryJoins()
|
||||||
|
{
|
||||||
|
return queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource withQueryJoins(List<QueryJoin> queryJoins)
|
||||||
|
{
|
||||||
|
this.queryJoins = queryJoins;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryJoins
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource withQueryJoin(QueryJoin queryJoin)
|
||||||
|
{
|
||||||
|
if(this.queryJoins == null)
|
||||||
|
{
|
||||||
|
this.queryJoins = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.queryJoins.add(queryJoin);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,27 @@ public class QReportField
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportField()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportField(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||||
@ -150,6 +151,21 @@ public class QReportMetaData implements QAppChildMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportMetaData withInputField(QFieldMetaData inputField)
|
||||||
|
{
|
||||||
|
if(this.inputFields == null)
|
||||||
|
{
|
||||||
|
this.inputFields = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.inputFields.add(inputField);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for processName
|
** Getter for processName
|
||||||
**
|
**
|
||||||
@ -218,6 +234,22 @@ public class QReportMetaData implements QAppChildMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for dataSources
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportMetaData withDataSource(QReportDataSource dataSource)
|
||||||
|
{
|
||||||
|
if(this.dataSources == null)
|
||||||
|
{
|
||||||
|
this.dataSources = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.dataSources.add(dataSource);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for views
|
** Getter for views
|
||||||
**
|
**
|
||||||
@ -252,6 +284,22 @@ public class QReportMetaData implements QAppChildMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for views
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportMetaData withView(QReportView view)
|
||||||
|
{
|
||||||
|
if(this.views == null)
|
||||||
|
{
|
||||||
|
this.views = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.views.add(view);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMeta
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -63,6 +64,21 @@ public class RunReportForRecordProcess
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Create a process meta data builder for this type of process, pre-populated
|
||||||
|
** with attributes based on a given report.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static Builder processMetaDataBuilder(QReportMetaData reportMetaData)
|
||||||
|
{
|
||||||
|
return (new Builder(defineProcessMetaData())
|
||||||
|
.withProcessName(reportMetaData.getProcessName())
|
||||||
|
.withReportName(reportMetaData.getName())
|
||||||
|
.withTableName(reportMetaData.getDataSources().get(0).getSourceTable())
|
||||||
|
.withIcon(reportMetaData.getIcon()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -55,6 +55,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
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.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||||
@ -1344,11 +1345,35 @@ class QInstanceValidatorTest
|
|||||||
@Test
|
@Test
|
||||||
void testReportDataSourceNames()
|
void testReportDataSourceNames()
|
||||||
{
|
{
|
||||||
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).setName(null),
|
assertValidationFailureReasons((qInstance) ->
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// enricher will give us a default name if only 1 data source, so, set 1st one to null name, then add a second //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QReportMetaData report = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON);
|
||||||
|
report.setDataSources(new ArrayList<>(report.getDataSources()));
|
||||||
|
report.getDataSources().get(0).setName(null);
|
||||||
|
report.getDataSources().add(new QReportDataSource()
|
||||||
|
.withName("2nd")
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
);
|
||||||
|
},
|
||||||
"Missing name for a dataSource",
|
"Missing name for a dataSource",
|
||||||
"unrecognized dataSourceName");
|
"unrecognized dataSourceName");
|
||||||
|
|
||||||
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).setName(""),
|
assertValidationFailureReasons((qInstance) ->
|
||||||
|
{
|
||||||
|
///////////////////////////////////
|
||||||
|
// same as above, but "" vs null //
|
||||||
|
///////////////////////////////////
|
||||||
|
QReportMetaData report = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON);
|
||||||
|
report.setDataSources(new ArrayList<>(report.getDataSources()));
|
||||||
|
report.getDataSources().get(0).setName("");
|
||||||
|
report.getDataSources().add(new QReportDataSource()
|
||||||
|
.withName("2nd")
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
);
|
||||||
|
},
|
||||||
"Missing name for a dataSource",
|
"Missing name for a dataSource",
|
||||||
"unrecognized dataSourceName");
|
"unrecognized dataSourceName");
|
||||||
|
|
||||||
|
@ -27,7 +27,9 @@ import java.sql.Connection;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
@ -36,12 +38,17 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
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.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
@ -168,9 +175,88 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
protected String makeWhereClause(QTableMetaData table, QQueryFilter filter, List<Serializable> params) throws IllegalArgumentException
|
protected String makeFromClause(QInstance instance, String tableName, JoinsContext joinsContext) throws QException
|
||||||
{
|
{
|
||||||
String clause = makeWhereClause(table, filter.getCriteria(), filter.getBooleanOperator(), params);
|
StringBuilder rs = new StringBuilder(escapeIdentifier(getTableName(instance.getTable(tableName))) + " AS " + escapeIdentifier(tableName));
|
||||||
|
|
||||||
|
for(QueryJoin queryJoin : joinsContext.getQueryJoins())
|
||||||
|
{
|
||||||
|
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
|
||||||
|
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
||||||
|
|
||||||
|
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 //
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
List<String> joinClauseList = new ArrayList<>();
|
||||||
|
String leftTableName = joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getLeftTableOrAlias());
|
||||||
|
QJoinMetaData joinMetaData = Objects.requireNonNullElseGet(queryJoin.getJoinMetaData(), () -> findJoinMetaData(instance, leftTableName, queryJoin.getRightTable()));
|
||||||
|
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();
|
||||||
|
|
||||||
|
joinClauseList.add(escapeIdentifier(leftTableOrAlias)
|
||||||
|
+ "." + escapeIdentifier(getColumnName(leftTable.getField(joinOn.getLeftField())))
|
||||||
|
+ " = " + escapeIdentifier(aliasOrRightTable)
|
||||||
|
+ "." + escapeIdentifier(getColumnName((rightTable.getField(joinOn.getRightField())))));
|
||||||
|
}
|
||||||
|
rs.append(" ON ").append(StringUtils.join(" AND ", joinClauseList));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rs.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private QJoinMetaData findJoinMetaData(QInstance instance, String leftTable, String rightTable)
|
||||||
|
{
|
||||||
|
List<QJoinMetaData> matches = new ArrayList<>();
|
||||||
|
for(QJoinMetaData join : instance.getJoins().values())
|
||||||
|
{
|
||||||
|
if(join.getLeftTable().equals(leftTable) && join.getRightTable().equals(rightTable))
|
||||||
|
{
|
||||||
|
matches.add(join);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// look in both directions! //
|
||||||
|
//////////////////////////////
|
||||||
|
if(join.getRightTable().equals(leftTable) && join.getLeftTable().equals(rightTable))
|
||||||
|
{
|
||||||
|
matches.add(join.flip());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(matches.size() == 1)
|
||||||
|
{
|
||||||
|
return (matches.get(0));
|
||||||
|
}
|
||||||
|
else if(matches.size() > 1)
|
||||||
|
{
|
||||||
|
throw (new RuntimeException("More than 1 join was found between [" + leftTable + "] and [" + rightTable + "]. Specify which one in your QueryJoin."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected String makeWhereClause(QInstance instance, QTableMetaData table, JoinsContext joinsContext, QQueryFilter filter, List<Serializable> params) throws IllegalArgumentException, QException
|
||||||
|
{
|
||||||
|
String clause = makeSimpleWhereClause(instance, table, joinsContext, filter.getCriteria(), filter.getBooleanOperator(), params);
|
||||||
if(!CollectionUtils.nullSafeHasContents(filter.getSubFilters()))
|
if(!CollectionUtils.nullSafeHasContents(filter.getSubFilters()))
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
@ -189,7 +275,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
}
|
}
|
||||||
for(QQueryFilter subFilter : filter.getSubFilters())
|
for(QQueryFilter subFilter : filter.getSubFilters())
|
||||||
{
|
{
|
||||||
String subClause = makeWhereClause(table, subFilter, params);
|
String subClause = makeWhereClause(instance, table, joinsContext, subFilter, params);
|
||||||
if(StringUtils.hasContent(subClause))
|
if(StringUtils.hasContent(subClause))
|
||||||
{
|
{
|
||||||
clauses.add("(" + subClause + ")");
|
clauses.add("(" + subClause + ")");
|
||||||
@ -203,14 +289,16 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private String makeWhereClause(QTableMetaData table, List<QFilterCriteria> criteria, QQueryFilter.BooleanOperator booleanOperator, List<Serializable> params) throws IllegalArgumentException
|
private String makeSimpleWhereClause(QInstance instance, QTableMetaData table, JoinsContext joinsContext, List<QFilterCriteria> criteria, QQueryFilter.BooleanOperator booleanOperator, List<Serializable> params) throws IllegalArgumentException
|
||||||
{
|
{
|
||||||
List<String> clauses = new ArrayList<>();
|
List<String> clauses = new ArrayList<>();
|
||||||
for(QFilterCriteria criterion : criteria)
|
for(QFilterCriteria criterion : criteria)
|
||||||
{
|
{
|
||||||
QFieldMetaData field = table.getField(criterion.getFieldName());
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(criterion.getFieldName());
|
||||||
|
|
||||||
List<Serializable> values = criterion.getValues() == null ? new ArrayList<>() : new ArrayList<>(criterion.getValues());
|
List<Serializable> values = criterion.getValues() == null ? new ArrayList<>() : new ArrayList<>(criterion.getValues());
|
||||||
String column = getColumnName(field);
|
QFieldMetaData field = fieldAndTableNameOrAlias.field();
|
||||||
|
String column = escapeIdentifier(fieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(getColumnName(field));
|
||||||
String clause = column;
|
String clause = column;
|
||||||
Integer expectedNoOfParams = null;
|
Integer expectedNoOfParams = null;
|
||||||
switch(criterion.getOperator())
|
switch(criterion.getOperator())
|
||||||
@ -360,15 +448,29 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
throw new IllegalArgumentException("Unexpected operator: " + criterion.getOperator());
|
throw new IllegalArgumentException("Unexpected operator: " + criterion.getOperator());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clauses.add("(" + clause + ")");
|
|
||||||
if(expectedNoOfParams != null)
|
if(expectedNoOfParams != null)
|
||||||
{
|
{
|
||||||
if(!expectedNoOfParams.equals(values.size()))
|
if(expectedNoOfParams.equals(1) && StringUtils.hasContent(criterion.getOtherFieldName()))
|
||||||
|
{
|
||||||
|
JoinsContext.FieldAndTableNameOrAlias otherFieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(criterion.getOtherFieldName());
|
||||||
|
|
||||||
|
String otherColumn = escapeIdentifier(otherFieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(getColumnName(otherFieldAndTableNameOrAlias.field()));
|
||||||
|
clause = clause.replace("?", otherColumn);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure we don't add any values in this case, just in case... //
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
values = Collections.emptyList();
|
||||||
|
}
|
||||||
|
else if(!expectedNoOfParams.equals(values.size()))
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("Incorrect number of values given for criteria [" + field.getName() + "]");
|
throw new IllegalArgumentException("Incorrect number of values given for criteria [" + field.getName() + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clauses.add("(" + clause + ")");
|
||||||
|
|
||||||
params.addAll(values);
|
params.addAll(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,7 +572,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
protected String makeOrderByClause(QTableMetaData table, List<QFilterOrderBy> orderBys)
|
protected String makeOrderByClause(QTableMetaData table, List<QFilterOrderBy> orderBys, JoinsContext joinsContext)
|
||||||
{
|
{
|
||||||
List<String> clauses = new ArrayList<>();
|
List<String> clauses = new ArrayList<>();
|
||||||
|
|
||||||
@ -485,9 +587,11 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QFieldMetaData field = table.getField(orderBy.getFieldName());
|
JoinsContext.FieldAndTableNameOrAlias otherFieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(orderBy.getFieldName());
|
||||||
String column = escapeIdentifier(getColumnName(field));
|
|
||||||
clauses.add(column + " " + ascOrDesc);
|
QFieldMetaData field = otherFieldAndTableNameOrAlias.field();
|
||||||
|
String column = getColumnName(field);
|
||||||
|
clauses.add(escapeIdentifier(otherFieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(column) + " " + ascOrDesc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (String.join(", ", clauses));
|
return (String.join(", ", clauses));
|
||||||
|
@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateIn
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOperator;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateResult;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateResult;
|
||||||
|
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.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
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.QFieldType;
|
||||||
@ -61,29 +62,30 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QTableMetaData table = aggregateInput.getTable();
|
QTableMetaData table = aggregateInput.getTable();
|
||||||
String tableName = getTableName(table);
|
|
||||||
|
|
||||||
List<String> selectClauses = buildSelectClauses(aggregateInput);
|
JoinsContext joinsContext = new JoinsContext(aggregateInput.getInstance(), table.getName(), aggregateInput.getQueryJoins());
|
||||||
|
String fromClause = makeFromClause(aggregateInput.getInstance(), table.getName(), joinsContext);
|
||||||
|
List<String> selectClauses = buildSelectClauses(aggregateInput, joinsContext);
|
||||||
|
|
||||||
String sql = "SELECT " + StringUtils.join(", ", selectClauses)
|
String sql = "SELECT " + StringUtils.join(", ", selectClauses)
|
||||||
+ " FROM " + escapeIdentifier(tableName);
|
+ " FROM " + fromClause;
|
||||||
|
|
||||||
QQueryFilter filter = aggregateInput.getFilter();
|
QQueryFilter filter = aggregateInput.getFilter();
|
||||||
List<Serializable> params = new ArrayList<>();
|
List<Serializable> params = new ArrayList<>();
|
||||||
if(filter != null && filter.hasAnyCriteria())
|
if(filter != null && filter.hasAnyCriteria())
|
||||||
{
|
{
|
||||||
sql += " WHERE " + makeWhereClause(table, filter, params);
|
sql += " WHERE " + makeWhereClause(aggregateInput.getInstance(), table, joinsContext, filter, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(CollectionUtils.nullSafeHasContents(aggregateInput.getGroupByFieldNames()))
|
if(CollectionUtils.nullSafeHasContents(aggregateInput.getGroupByFieldNames()))
|
||||||
{
|
{
|
||||||
sql += " GROUP BY " + makeGroupByClause(aggregateInput);
|
sql += " GROUP BY " + makeGroupByClause(aggregateInput, joinsContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
||||||
{
|
{
|
||||||
sql += " ORDER BY " + makeOrderByClause(table, filter.getOrderBys());
|
sql += " ORDER BY " + makeOrderByClause(table, filter.getOrderBys(), joinsContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo sql customization - can edit sql and/or param list
|
// todo sql customization - can edit sql and/or param list
|
||||||
@ -105,13 +107,16 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
int selectionIndex = 1;
|
int selectionIndex = 1;
|
||||||
for(String groupByFieldName : CollectionUtils.nonNullList(aggregateInput.getGroupByFieldNames()))
|
for(String groupByFieldName : CollectionUtils.nonNullList(aggregateInput.getGroupByFieldNames()))
|
||||||
{
|
{
|
||||||
Serializable value = getFieldValueFromResultSet(table.getField(groupByFieldName), resultSet, selectionIndex++);
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(groupByFieldName);
|
||||||
|
Serializable value = getFieldValueFromResultSet(fieldAndTableNameOrAlias.field(), resultSet, selectionIndex++);
|
||||||
result.withGroupByValue(groupByFieldName, value);
|
result.withGroupByValue(groupByFieldName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Aggregate aggregate : aggregateInput.getAggregates())
|
for(Aggregate aggregate : aggregateInput.getAggregates())
|
||||||
{
|
{
|
||||||
QFieldMetaData field = table.getField(aggregate.getFieldName());
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(aggregate.getFieldName());
|
||||||
|
QFieldMetaData field = fieldAndTableNameOrAlias.field();
|
||||||
|
|
||||||
if(field.getType().equals(QFieldType.INTEGER) && aggregate.getOperator().equals(AggregateOperator.AVG))
|
if(field.getType().equals(QFieldType.INTEGER) && aggregate.getOperator().equals(AggregateOperator.AVG))
|
||||||
{
|
{
|
||||||
field = new QFieldMetaData().withType(QFieldType.DECIMAL);
|
field = new QFieldMetaData().withType(QFieldType.DECIMAL);
|
||||||
@ -139,19 +144,20 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private List<String> buildSelectClauses(AggregateInput aggregateInput)
|
private List<String> buildSelectClauses(AggregateInput aggregateInput, JoinsContext joinsContext)
|
||||||
{
|
{
|
||||||
QTableMetaData table = aggregateInput.getTable();
|
List<String> rs = new ArrayList<>();
|
||||||
List<String> rs = new ArrayList<>();
|
|
||||||
|
|
||||||
for(String groupByFieldName : CollectionUtils.nonNullList(aggregateInput.getGroupByFieldNames()))
|
for(String groupByFieldName : CollectionUtils.nonNullList(aggregateInput.getGroupByFieldNames()))
|
||||||
{
|
{
|
||||||
rs.add(escapeIdentifier(getColumnName(table.getField(groupByFieldName))));
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(groupByFieldName);
|
||||||
|
rs.add(escapeIdentifier(fieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(getColumnName(fieldAndTableNameOrAlias.field())));
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Aggregate aggregate : aggregateInput.getAggregates())
|
for(Aggregate aggregate : aggregateInput.getAggregates())
|
||||||
{
|
{
|
||||||
rs.add(aggregate.getOperator() + "(" + escapeIdentifier(getColumnName(table.getField(aggregate.getFieldName()))) + ")");
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(aggregate.getFieldName());
|
||||||
|
rs.add(aggregate.getOperator() + "(" + escapeIdentifier(fieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(getColumnName(fieldAndTableNameOrAlias.field())) + ")");
|
||||||
}
|
}
|
||||||
return (rs);
|
return (rs);
|
||||||
}
|
}
|
||||||
@ -161,13 +167,13 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private String makeGroupByClause(AggregateInput aggregateInput)
|
private String makeGroupByClause(AggregateInput aggregateInput, JoinsContext joinsContext)
|
||||||
{
|
{
|
||||||
QTableMetaData table = aggregateInput.getTable();
|
List<String> columns = new ArrayList<>();
|
||||||
List<String> columns = new ArrayList<>();
|
|
||||||
for(String groupByFieldName : aggregateInput.getGroupByFieldNames())
|
for(String groupByFieldName : aggregateInput.getGroupByFieldNames())
|
||||||
{
|
{
|
||||||
columns.add(escapeIdentifier(getColumnName(table.getField(groupByFieldName))));
|
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(groupByFieldName);
|
||||||
|
columns.add(escapeIdentifier(fieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(getColumnName(fieldAndTableNameOrAlias.field())));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (StringUtils.join(",", columns));
|
return (StringUtils.join(",", columns));
|
||||||
|
@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
|||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||||
|
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.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||||
@ -54,16 +55,18 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QTableMetaData table = countInput.getTable();
|
QTableMetaData table = countInput.getTable();
|
||||||
String tableName = getTableName(table);
|
|
||||||
|
|
||||||
String sql = "SELECT count(*) as record_count FROM " + escapeIdentifier(tableName);
|
JoinsContext joinsContext = new JoinsContext(countInput.getInstance(), countInput.getTableName(), countInput.getQueryJoins());
|
||||||
|
|
||||||
|
String sql = "SELECT count(*) as record_count FROM "
|
||||||
|
+ makeFromClause(countInput.getInstance(), table.getName(), joinsContext);
|
||||||
|
|
||||||
QQueryFilter filter = countInput.getFilter();
|
QQueryFilter filter = countInput.getFilter();
|
||||||
List<Serializable> params = new ArrayList<>();
|
List<Serializable> params = new ArrayList<>();
|
||||||
if(filter != null && filter.hasAnyCriteria())
|
if(filter != null && filter.hasAnyCriteria())
|
||||||
{
|
{
|
||||||
sql += " WHERE " + makeWhereClause(table, filter, params);
|
sql += " WHERE " + makeWhereClause(countInput.getInstance(), table, joinsContext, filter, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo sql customization - can edit sql and/or param list
|
// todo sql customization - can edit sql and/or param list
|
||||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||||
@ -32,6 +33,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
|||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||||
|
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.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
@ -258,8 +260,9 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
|
|||||||
List<Serializable> params = new ArrayList<>();
|
List<Serializable> params = new ArrayList<>();
|
||||||
QTableMetaData table = deleteInput.getTable();
|
QTableMetaData table = deleteInput.getTable();
|
||||||
|
|
||||||
String tableName = getTableName(table);
|
String tableName = getTableName(table);
|
||||||
String whereClause = makeWhereClause(table, filter, params);
|
JoinsContext joinsContext = new JoinsContext(deleteInput.getInstance(), table.getName(), Collections.emptyList());
|
||||||
|
String whereClause = makeWhereClause(deleteInput.getInstance(), table, joinsContext, filter, params);
|
||||||
|
|
||||||
// todo sql customization - can edit sql and/or param list?
|
// todo sql customization - can edit sql and/or param list?
|
||||||
String sql = "DELETE FROM "
|
String sql = "DELETE FROM "
|
||||||
|
@ -34,10 +34,13 @@ import java.util.List;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
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.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
@ -64,35 +67,34 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
QTableMetaData table = queryInput.getTable();
|
QTableMetaData table = queryInput.getTable();
|
||||||
String tableName = getTableName(table);
|
String tableName = queryInput.getTableName();
|
||||||
|
|
||||||
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values());
|
StringBuilder sql = new StringBuilder("SELECT ").append(makeSelectClause(queryInput.getInstance(), tableName, queryInput.getQueryJoins()));
|
||||||
String columns = fieldList.stream()
|
|
||||||
.map(this::getColumnName)
|
|
||||||
.collect(Collectors.joining(", "));
|
|
||||||
|
|
||||||
String sql = "SELECT " + columns + " FROM " + escapeIdentifier(tableName);
|
JoinsContext joinsContext = new JoinsContext(queryInput.getInstance(), tableName, queryInput.getQueryJoins());
|
||||||
|
sql.append(" FROM ").append(makeFromClause(queryInput.getInstance(), tableName, joinsContext));
|
||||||
|
|
||||||
QQueryFilter filter = queryInput.getFilter();
|
QQueryFilter filter = queryInput.getFilter();
|
||||||
List<Serializable> params = new ArrayList<>();
|
List<Serializable> params = new ArrayList<>();
|
||||||
|
|
||||||
if(filter != null && filter.hasAnyCriteria())
|
if(filter != null && filter.hasAnyCriteria())
|
||||||
{
|
{
|
||||||
sql += " WHERE " + makeWhereClause(table, filter, params);
|
sql.append(" WHERE ").append(makeWhereClause(queryInput.getInstance(), table, joinsContext, filter, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
||||||
{
|
{
|
||||||
sql += " ORDER BY " + makeOrderByClause(table, filter.getOrderBys());
|
sql.append(" ORDER BY ").append(makeOrderByClause(table, filter.getOrderBys(), joinsContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queryInput.getLimit() != null)
|
if(queryInput.getLimit() != null)
|
||||||
{
|
{
|
||||||
sql += " LIMIT " + queryInput.getLimit();
|
sql.append(" LIMIT ").append(queryInput.getLimit());
|
||||||
|
|
||||||
if(queryInput.getSkip() != null)
|
if(queryInput.getSkip() != null)
|
||||||
{
|
{
|
||||||
// todo - other sql grammars?
|
// todo - other sql grammars?
|
||||||
sql += " OFFSET " + queryInput.getSkip();
|
sql.append(" OFFSET ").append(queryInput.getSkip());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,10 +113,31 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
needToCloseConnection = true;
|
needToCloseConnection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// build the list of fields that will be processed in the result-set loop //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values());
|
||||||
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryInput.getQueryJoins()))
|
||||||
|
{
|
||||||
|
if(queryJoin.getSelect())
|
||||||
|
{
|
||||||
|
QTableMetaData joinTable = queryInput.getInstance().getTable(queryJoin.getRightTable());
|
||||||
|
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
||||||
|
for(QFieldMetaData joinField : joinTable.getFields().values())
|
||||||
|
{
|
||||||
|
fieldList.add(joinField.clone().withName(tableNameOrAlias + "." + joinField.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
//////////////////////////////////////////////
|
||||||
PreparedStatement statement = createStatement(connection, sql, queryInput);
|
// execute the query - iterate over results //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||||
|
System.out.println(sql);
|
||||||
|
PreparedStatement statement = createStatement(connection, sql.toString(), queryInput);
|
||||||
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
||||||
{
|
{
|
||||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||||
@ -162,6 +185,42 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private String makeSelectClause(QInstance instance, String tableName, List<QueryJoin> queryJoins) throws QException
|
||||||
|
{
|
||||||
|
QTableMetaData table = instance.getTable(tableName);
|
||||||
|
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values());
|
||||||
|
String columns = fieldList.stream()
|
||||||
|
.map(field -> escapeIdentifier(tableName) + "." + escapeIdentifier(getColumnName(field)))
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
StringBuilder rs = new StringBuilder(columns);
|
||||||
|
|
||||||
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||||
|
{
|
||||||
|
if(queryJoin.getSelect())
|
||||||
|
{
|
||||||
|
QTableMetaData joinTable = instance.getTable(queryJoin.getRightTable());
|
||||||
|
String tableNameOrAlias = queryJoin.getAliasOrRightTable();
|
||||||
|
if(joinTable == null)
|
||||||
|
{
|
||||||
|
throw new QException("Requested join table [" + queryJoin.getRightTable() + "] is not a defined table.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<QFieldMetaData> joinFieldList = new ArrayList<>(joinTable.getFields().values());
|
||||||
|
String joinColumns = joinFieldList.stream()
|
||||||
|
.map(field -> escapeIdentifier(tableNameOrAlias) + "." + escapeIdentifier(getColumnName(field)))
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
rs.append(", ").append(joinColumns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rs.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -29,6 +29,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
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.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.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
|
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSActionTest;
|
||||||
@ -45,9 +48,15 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class TestUtils
|
public class TestUtils
|
||||||
{
|
{
|
||||||
|
|
||||||
public static final String DEFAULT_BACKEND_NAME = "default";
|
public static final String DEFAULT_BACKEND_NAME = "default";
|
||||||
|
|
||||||
|
public static final String TABLE_NAME_PERSON = "personTable";
|
||||||
|
public static final String TABLE_NAME_PERSONAL_ID_CARD = "personalIdCard";
|
||||||
|
public static final String TABLE_NAME_STORE = "store";
|
||||||
|
public static final String TABLE_NAME_ORDER = "order";
|
||||||
|
public static final String TABLE_NAME_ITEM = "item";
|
||||||
|
public static final String TABLE_NAME_ORDER_LINE = "orderLine";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -81,6 +90,9 @@ public class TestUtils
|
|||||||
QInstance qInstance = new QInstance();
|
QInstance qInstance = new QInstance();
|
||||||
qInstance.addBackend(defineBackend());
|
qInstance.addBackend(defineBackend());
|
||||||
qInstance.addTable(defineTablePerson());
|
qInstance.addTable(defineTablePerson());
|
||||||
|
qInstance.addTable(defineTablePersonalIdCard());
|
||||||
|
qInstance.addJoin(defineJoinPersonAndPersonalIdCard());
|
||||||
|
addOmsTablesAndJoins(qInstance);
|
||||||
qInstance.setAuthentication(defineAuthentication());
|
qInstance.setAuthentication(defineAuthentication());
|
||||||
return (qInstance);
|
return (qInstance);
|
||||||
}
|
}
|
||||||
@ -121,9 +133,9 @@ public class TestUtils
|
|||||||
public static QTableMetaData defineTablePerson()
|
public static QTableMetaData defineTablePerson()
|
||||||
{
|
{
|
||||||
return new QTableMetaData()
|
return new QTableMetaData()
|
||||||
.withName("a-person") // use this name, so it isn't the same as the actual database-table name (which must come from the backend details)
|
.withName(TABLE_NAME_PERSON)
|
||||||
.withLabel("Person")
|
.withLabel("Person")
|
||||||
.withBackendName(defineBackend().getName())
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
.withPrimaryKeyField("id")
|
.withPrimaryKeyField("id")
|
||||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date"))
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date"))
|
||||||
@ -139,4 +151,136 @@ public class TestUtils
|
|||||||
.withTableName("person"));
|
.withTableName("person"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Define a 1:1 table with Person.
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QTableMetaData defineTablePersonalIdCard()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(TABLE_NAME_PERSONAL_ID_CARD)
|
||||||
|
.withLabel("Personal Id Card")
|
||||||
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
|
.withBackendDetails(new RDBMSTableBackendDetails()
|
||||||
|
.withTableName("personal_id_card"))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date"))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date"))
|
||||||
|
.withField(new QFieldMetaData("personId", QFieldType.INTEGER).withBackendName("person_id"))
|
||||||
|
.withField(new QFieldMetaData("idNumber", QFieldType.STRING).withBackendName("id_number"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QJoinMetaData defineJoinPersonAndPersonalIdCard()
|
||||||
|
{
|
||||||
|
return new QJoinMetaData()
|
||||||
|
.withLeftTable(TABLE_NAME_PERSON)
|
||||||
|
.withRightTable(TABLE_NAME_PERSONAL_ID_CARD)
|
||||||
|
.withInferredName()
|
||||||
|
.withType(JoinType.ONE_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("id", "personId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void addOmsTablesAndJoins(QInstance qInstance)
|
||||||
|
{
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_STORE, "store")
|
||||||
|
.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"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
|
||||||
|
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
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("quantity", QFieldType.INTEGER))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinStore")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_STORE)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("storeId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinBillToPerson")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_PERSON)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("billToPersonId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinShipToPerson")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_PERSON)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("shipToPersonId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("itemJoinStore")
|
||||||
|
.withLeftTable(TABLE_NAME_ITEM)
|
||||||
|
.withRightTable(TABLE_NAME_STORE)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("storeId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinOrderLine")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_ORDER_LINE)
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withJoinOn(new JoinOn("id", "orderId"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderLineJoinItem")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER_LINE)
|
||||||
|
.withRightTable(TABLE_NAME_ITEM)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("sku", "sku"))
|
||||||
|
.withJoinOn(new JoinOn("storeId", "storeId"))
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QTableMetaData defineBaseTable(String tableName, String backendTableName)
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(tableName)
|
||||||
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
|
.withBackendDetails(new RDBMSTableBackendDetails().withTableName(backendTableName))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,16 @@ 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.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -294,6 +297,71 @@ public class RDBMSAggregateActionTest extends RDBMSActionTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOmsJoinAggregate() throws Exception
|
||||||
|
{
|
||||||
|
AggregateInput aggregateInput = new AggregateInput(TestUtils.defineInstance());
|
||||||
|
Aggregate sumOfQuantity = new Aggregate(TestUtils.TABLE_NAME_ORDER_LINE + ".quantity", AggregateOperator.SUM);
|
||||||
|
aggregateInput.setSession(new QSession());
|
||||||
|
aggregateInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
aggregateInput.withAggregate(sumOfQuantity);
|
||||||
|
aggregateInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER, TestUtils.TABLE_NAME_ORDER_LINE));
|
||||||
|
|
||||||
|
AggregateOutput aggregateOutput = new RDBMSAggregateAction().execute(aggregateInput);
|
||||||
|
AggregateResult aggregateResult = aggregateOutput.getResults().get(0);
|
||||||
|
Assertions.assertEquals(43, aggregateResult.getAggregateValue(sumOfQuantity));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOmsJoinGroupBy() throws Exception
|
||||||
|
{
|
||||||
|
AggregateInput aggregateInput = new AggregateInput(TestUtils.defineInstance());
|
||||||
|
Aggregate sumOfQuantity = new Aggregate(TestUtils.TABLE_NAME_ORDER_LINE + ".quantity", AggregateOperator.SUM);
|
||||||
|
aggregateInput.setSession(new QSession());
|
||||||
|
aggregateInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
aggregateInput.withAggregate(sumOfQuantity);
|
||||||
|
aggregateInput.withGroupByFieldName(TestUtils.TABLE_NAME_ORDER_LINE + ".sku");
|
||||||
|
aggregateInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER, TestUtils.TABLE_NAME_ORDER_LINE));
|
||||||
|
|
||||||
|
AggregateOutput aggregateOutput = new RDBMSAggregateAction().execute(aggregateInput);
|
||||||
|
assertEquals(6, aggregateOutput.getResults().size());
|
||||||
|
assertSkuQuantity("QM-1", 30, aggregateOutput.getResults());
|
||||||
|
assertSkuQuantity("QM-2", 1, aggregateOutput.getResults());
|
||||||
|
assertSkuQuantity("QM-3", 1, aggregateOutput.getResults());
|
||||||
|
assertSkuQuantity("QRU-1", 3, aggregateOutput.getResults());
|
||||||
|
assertSkuQuantity("QRU-2", 2, aggregateOutput.getResults());
|
||||||
|
assertSkuQuantity("QD-1", 6, aggregateOutput.getResults());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void assertSkuQuantity(String sku, int quantity, List<AggregateResult> results)
|
||||||
|
{
|
||||||
|
for(AggregateResult result : results)
|
||||||
|
{
|
||||||
|
if(result.getGroupByValue("orderLine.sku").equals(sku))
|
||||||
|
{
|
||||||
|
assertEquals(quantity, result.getAggregateValues().values().iterator().next());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail("Didn't find SKU " + sku + " in aggregate results");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -23,16 +23,19 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
|
|||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
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.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -60,7 +63,7 @@ public class RDBMSCountActionTest extends RDBMSActionTest
|
|||||||
{
|
{
|
||||||
CountInput countInput = initCountRequest();
|
CountInput countInput = initCountRequest();
|
||||||
CountOutput countOutput = new RDBMSCountAction().execute(countInput);
|
CountOutput countOutput = new RDBMSCountAction().execute(countInput);
|
||||||
Assertions.assertEquals(5, countOutput.getCount(), "Unfiltered query should find all rows");
|
assertEquals(5, countOutput.getCount(), "Unfiltered query should find all rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -81,7 +84,7 @@ public class RDBMSCountActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(email)))
|
.withValues(List.of(email)))
|
||||||
);
|
);
|
||||||
CountOutput countOutput = new RDBMSCountAction().execute(countInput);
|
CountOutput countOutput = new RDBMSCountAction().execute(countInput);
|
||||||
Assertions.assertEquals(1, countOutput.getCount(), "Expected # of rows");
|
assertEquals(1, countOutput.getCount(), "Expected # of rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -102,7 +105,7 @@ public class RDBMSCountActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(email)))
|
.withValues(List.of(email)))
|
||||||
);
|
);
|
||||||
CountOutput countOutput = new RDBMSCountAction().execute(countInput);
|
CountOutput countOutput = new RDBMSCountAction().execute(countInput);
|
||||||
Assertions.assertEquals(4, countOutput.getCount(), "Expected # of rows");
|
assertEquals(4, countOutput.getCount(), "Expected # of rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -114,8 +117,66 @@ public class RDBMSCountActionTest extends RDBMSActionTest
|
|||||||
{
|
{
|
||||||
CountInput countInput = new CountInput();
|
CountInput countInput = new CountInput();
|
||||||
countInput.setInstance(TestUtils.defineInstance());
|
countInput.setInstance(TestUtils.defineInstance());
|
||||||
|
countInput.setSession(new QSession());
|
||||||
countInput.setTableName(TestUtils.defineTablePerson().getName());
|
countInput.setTableName(TestUtils.defineTablePerson().getName());
|
||||||
return countInput;
|
return countInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOneToOneInnerJoinWithoutWhere() throws QException
|
||||||
|
{
|
||||||
|
CountInput countInput = initCountRequest();
|
||||||
|
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD));
|
||||||
|
CountOutput countOutput = new CountAction().execute(countInput);
|
||||||
|
assertEquals(3, countOutput.getCount(), "Join count should find 3 rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOneToOneLeftJoinWithoutWhere() throws QException
|
||||||
|
{
|
||||||
|
CountInput countInput = initCountRequest();
|
||||||
|
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.LEFT));
|
||||||
|
CountOutput countOutput = new CountAction().execute(countInput);
|
||||||
|
assertEquals(5, countOutput.getCount(), "Left Join count should find 5 rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOneToOneRightJoinWithoutWhere() throws QException
|
||||||
|
{
|
||||||
|
CountInput countInput = initCountRequest();
|
||||||
|
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.RIGHT));
|
||||||
|
CountOutput countOutput = new CountAction().execute(countInput);
|
||||||
|
assertEquals(6, countOutput.getCount(), "Right Join count should find 6 rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOneToOneInnerJoinWithWhere() throws QException
|
||||||
|
{
|
||||||
|
CountInput countInput = initCountRequest();
|
||||||
|
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true));
|
||||||
|
countInput.setFilter(new QQueryFilter(new QFilterCriteria(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber", QCriteriaOperator.STARTS_WITH, "1980")));
|
||||||
|
CountOutput countOutput = new CountAction().execute(countInput);
|
||||||
|
assertEquals(2, countOutput.getCount(), "Right Join count should find 2 rows");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
|
|||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
@ -30,16 +31,21 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
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.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.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -67,7 +73,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
{
|
{
|
||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(5, queryOutput.getRecords().size(), "Unfiltered query should find all rows");
|
assertEquals(5, queryOutput.getRecords().size(), "Unfiltered query should find all rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,8 +94,8 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(email)))
|
.withValues(List.of(email)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertEquals(email, queryOutput.getRecords().get(0).getValueString("email"), "Should find expected email address");
|
assertEquals(email, queryOutput.getRecords().get(0).getValueString("email"), "Should find expected email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -110,7 +116,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(email)))
|
.withValues(List.of(email)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").equals(email)), "Should NOT find expected email address");
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").equals(email)), "Should NOT find expected email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +136,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(2, 4)))
|
.withValues(List.of(2, 4)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(2) || r.getValueInteger("id").equals(4)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(2) || r.getValueInteger("id").equals(4)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +156,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(2, 3, 4)))
|
.withValues(List.of(2, 3, 4)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +176,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of("darin")))
|
.withValues(List.of("darin")))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +196,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of("kelkhoff")))
|
.withValues(List.of("kelkhoff")))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +216,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of("gmail.com")))
|
.withValues(List.of("gmail.com")))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +236,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of("darin")))
|
.withValues(List.of("darin")))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +256,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of("kelkhoff")))
|
.withValues(List.of("kelkhoff")))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +276,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of("gmail.com")))
|
.withValues(List.of("gmail.com")))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +296,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(3)))
|
.withValues(List.of(3)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +316,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(2)))
|
.withValues(List.of(2)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +336,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(3)))
|
.withValues(List.of(3)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +356,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(4)))
|
.withValues(List.of(4)))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,7 +375,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withOperator(QCriteriaOperator.IS_BLANK)
|
.withOperator(QCriteriaOperator.IS_BLANK)
|
||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("birthDate") == null), "Should find expected row");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("birthDate") == null), "Should find expected row");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +394,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withOperator(QCriteriaOperator.IS_NOT_BLANK)
|
.withOperator(QCriteriaOperator.IS_NOT_BLANK)
|
||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(5, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(5, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("firstName") != null), "Should find expected rows");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("firstName") != null), "Should find expected rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +414,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(2, 4))
|
.withValues(List.of(2, 4))
|
||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(3, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(3, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(2) || r.getValueInteger("id").equals(3) || r.getValueInteger("id").equals(4)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(2) || r.getValueInteger("id").equals(3) || r.getValueInteger("id").equals(4)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,7 +434,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(2, 4))
|
.withValues(List.of(2, 4))
|
||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,7 +447,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
{
|
{
|
||||||
QueryInput queryInput = new QueryInput();
|
QueryInput queryInput = new QueryInput();
|
||||||
queryInput.setInstance(TestUtils.defineInstance());
|
queryInput.setInstance(TestUtils.defineInstance());
|
||||||
queryInput.setTableName(TestUtils.defineTablePerson().getName());
|
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
queryInput.setSession(new QSession());
|
queryInput.setSession(new QSession());
|
||||||
return queryInput;
|
return queryInput;
|
||||||
}
|
}
|
||||||
@ -459,7 +465,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
queryInput.setShouldGenerateDisplayValues(true);
|
queryInput.setShouldGenerateDisplayValues(true);
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(5, queryOutput.getRecords().size(), "Unfiltered query should find all rows");
|
assertEquals(5, queryOutput.getRecords().size(), "Unfiltered query should find all rows");
|
||||||
|
|
||||||
for(QRecord record : queryOutput.getRecords())
|
for(QRecord record : queryOutput.getRecords())
|
||||||
{
|
{
|
||||||
@ -479,7 +485,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
{
|
{
|
||||||
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
|
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
|
||||||
insertInput.setSession(new QSession());
|
insertInput.setSession(new QSession());
|
||||||
insertInput.setTableName(TestUtils.defineTablePerson().getName());
|
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
|
||||||
InsertAction insertAction = new InsertAction();
|
InsertAction insertAction = new InsertAction();
|
||||||
QBackendTransaction transaction = insertAction.openTransaction(insertInput);
|
QBackendTransaction transaction = insertAction.openTransaction(insertInput);
|
||||||
@ -493,12 +499,12 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
|
|
||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(5, queryOutput.getRecords().size(), "Query without the transaction should not see the new row.");
|
assertEquals(5, queryOutput.getRecords().size(), "Query without the transaction should not see the new row.");
|
||||||
|
|
||||||
queryInput = initQueryRequest();
|
queryInput = initQueryRequest();
|
||||||
queryInput.setTransaction(transaction);
|
queryInput.setTransaction(transaction);
|
||||||
queryOutput = new QueryAction().execute(queryInput);
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(6, queryOutput.getRecords().size(), "Query with the transaction should see the new row.");
|
assertEquals(6, queryOutput.getRecords().size(), "Query with the transaction should see the new row.");
|
||||||
|
|
||||||
transaction.rollback();
|
transaction.rollback();
|
||||||
}
|
}
|
||||||
@ -514,11 +520,11 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
QueryInput queryInput = initQueryRequest();
|
QueryInput queryInput = initQueryRequest();
|
||||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IN, List.of())));
|
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IN, List.of())));
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(0, queryOutput.getRecords().size(), "IN empty list should find nothing.");
|
assertEquals(0, queryOutput.getRecords().size(), "IN empty list should find nothing.");
|
||||||
|
|
||||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.NOT_IN, List.of())));
|
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.NOT_IN, List.of())));
|
||||||
queryOutput = new QueryAction().execute(queryInput);
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(5, queryOutput.getRecords().size(), "NOT_IN empty list should find everything.");
|
assertEquals(5, queryOutput.getRecords().size(), "NOT_IN empty list should find everything.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -536,7 +542,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Tim")))
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Tim")))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "OR should find 2 rows");
|
assertEquals(2, queryOutput.getRecords().size(), "OR should find 2 rows");
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin"));
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim"));
|
||||||
}
|
}
|
||||||
@ -564,7 +570,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(2, queryOutput.getRecords().size(), "Complex query should find 2 rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Complex query should find 2 rows");
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("lastName").equals("Maes"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("lastName").equals("Maes"));
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("lastName").equals("Kelkhoff"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("lastName").equals("Kelkhoff"));
|
||||||
}
|
}
|
||||||
@ -592,8 +598,322 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Complex query should find 1 row");
|
assertEquals(1, queryOutput.getRecords().size(), "Complex query should find 1 row");
|
||||||
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim") && r.getValueString("lastName").equals("Chamberlain"));
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim") && r.getValueString("lastName").equals("Chamberlain"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOneToOneInnerJoinWithoutWhere() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, 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"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("personalIdCard.idNumber").equals("19800515"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim") && r.getValueString("personalIdCard.idNumber").equals("19760528"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
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));
|
||||||
|
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"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("personalIdCard.idNumber").equals("19800515"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim") && r.getValueString("personalIdCard.idNumber").equals("19760528"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Garret") && r.getValue("personalIdCard.idNumber") == null);
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tyler") && r.getValue("personalIdCard.idNumber") == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
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));
|
||||||
|
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"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("personalIdCard.idNumber").equals("19800515"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim") && r.getValueString("personalIdCard.idNumber").equals("19760528"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValue("firstName") == null && r.getValueString("personalIdCard.idNumber").equals("123123123"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValue("firstName") == null && r.getValueString("personalIdCard.idNumber").equals("987987987"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValue("firstName") == null && r.getValueString("personalIdCard.idNumber").equals("456456456"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOneToOneInnerJoinWithWhere() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, 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");
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("personalIdCard.idNumber").equals("19800531"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("personalIdCard.idNumber").equals("19800515"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOneToOneInnerJoinWithOrderBy() throws QException
|
||||||
|
{
|
||||||
|
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.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");
|
||||||
|
List<String> idNumberListFromQuery = queryOutput.getRecords().stream().map(r -> r.getValueString(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber")).toList();
|
||||||
|
assertEquals(List.of("19760528", "19800515", "19800531"), idNumberListFromQuery);
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// repeat, sorted desc //
|
||||||
|
/////////////////////////
|
||||||
|
queryInput.setFilter(new QQueryFilter().withOrderBy(new QFilterOrderBy(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber", false)));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(3, queryOutput.getRecords().size(), "Join query should find 3 rows");
|
||||||
|
idNumberListFromQuery = queryOutput.getRecords().stream().map(r -> r.getValueString(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber")).toList();
|
||||||
|
assertEquals(List.of("19800531", "19800515", "19760528"), idNumberListFromQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** In the prime data, we've got 1 order line set up with an item from a different
|
||||||
|
** store than its order. Write a query to find such a case.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testFiveTableOmsJoinFindMismatchedStoreId() throws Exception
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput(TestUtils.defineInstance(), new QSession());
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER, TestUtils.TABLE_NAME_STORE).withAlias("orderStore").withSelect(true));
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER, TestUtils.TABLE_NAME_ORDER_LINE).withSelect(true));
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER_LINE, TestUtils.TABLE_NAME_ITEM).withSelect(true));
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ITEM, TestUtils.TABLE_NAME_STORE).withAlias("itemStore").withSelect(true));
|
||||||
|
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria().withFieldName("orderStore.id").withOperator(QCriteriaOperator.NOT_EQUALS).withOtherFieldName("item.storeId")));
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
QRecord qRecord = queryOutput.getRecords().get(0);
|
||||||
|
assertEquals(2, qRecord.getValueInteger("id"));
|
||||||
|
assertEquals(1, qRecord.getValueInteger("orderStore.id"));
|
||||||
|
assertEquals(2, qRecord.getValueInteger("itemStore.id"));
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// run the same setup, but this time, use the other-field-name as itemStore.id, instead of item.storeId //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria().withFieldName("orderStore.id").withOperator(QCriteriaOperator.NOT_EQUALS).withOtherFieldName("itemStore.id")));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
qRecord = queryOutput.getRecords().get(0);
|
||||||
|
assertEquals(2, qRecord.getValueInteger("id"));
|
||||||
|
assertEquals(1, qRecord.getValueInteger("orderStore.id"));
|
||||||
|
assertEquals(2, qRecord.getValueInteger("itemStore.id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOmsQueryByOrderLines() throws Exception
|
||||||
|
{
|
||||||
|
AtomicInteger orderLineCount = new AtomicInteger();
|
||||||
|
runTestSql("SELECT COUNT(*) from order_line", (rs) ->
|
||||||
|
{
|
||||||
|
rs.next();
|
||||||
|
orderLineCount.set(rs.getInt(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(orderLineCount.get(), queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
assertEquals(3, queryOutput.getRecords().stream().filter(r -> r.getValueInteger("order.id").equals(1)).count());
|
||||||
|
assertEquals(1, queryOutput.getRecords().stream().filter(r -> r.getValueInteger("order.id").equals(2)).count());
|
||||||
|
assertEquals(1, queryOutput.getRecords().stream().filter(r -> r.getValueInteger("orderId").equals(3)).count());
|
||||||
|
assertEquals(2, queryOutput.getRecords().stream().filter(r -> r.getValueInteger("orderId").equals(4)).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOmsQueryByPersons() throws Exception
|
||||||
|
{
|
||||||
|
QInstance instance = TestUtils.defineInstance();
|
||||||
|
QueryInput queryInput = new QueryInput(instance, new QSession());
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// 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)));
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(6, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// inner join on ship-to person should find 7 rows //
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
queryInput.withQueryJoins(List.of(new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withSelect(true)));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(7, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// inner join on both bill-to person and ship-to person should find 5 rows //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withAlias("shipToPerson").withSelect(true),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withAlias("billToPerson").withSelect(true)
|
||||||
|
));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(5, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// left join on both bill-to person and ship-to person should find 8 rows //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withType(QueryJoin.Type.LEFT).withAlias("shipToPerson").withSelect(true),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withType(QueryJoin.Type.LEFT).withAlias("billToPerson").withSelect(true)
|
||||||
|
));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(8, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// now join through to personalIdCard table too //
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
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("shipToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("shipToIdCard").withSelect(true)
|
||||||
|
));
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// look for billToPersons w/ idNumber starting with 1980 - should only be James and Darin (assert on that below). //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
.withCriteria(new QFilterCriteria("billToIdCard.idNumber", QCriteriaOperator.STARTS_WITH, "1980"))
|
||||||
|
);
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOmsQueryByPersonsExtraKelkhoffOrder() throws Exception
|
||||||
|
{
|
||||||
|
QInstance instance = TestUtils.defineInstance();
|
||||||
|
QueryInput queryInput = new QueryInput(instance, new QSession());
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// insert a second person w/ last name Kelkhoff, then an order for Darin Kelkhoff and this new Kelkhoff - //
|
||||||
|
// then query for orders w/ bill to person & ship to person both lastname = Kelkhoff, but different ids. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Integer specialOrderId = 1701;
|
||||||
|
runTestSql("INSERT INTO person (id, first_name, last_name, email) VALUES (6, 'Jimmy', 'Kelkhoff', 'dk@gmail.com')", null);
|
||||||
|
runTestSql("INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (" + specialOrderId + ", 1, 1, 6)", null);
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withType(QueryJoin.Type.LEFT).withAlias("shipToPerson").withSelect(true),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withType(QueryJoin.Type.LEFT).withAlias("billToPerson").withSelect(true)
|
||||||
|
));
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("shipToPerson.lastName").withOperator(QCriteriaOperator.EQUALS).withOtherFieldName("billToPerson.lastName"))
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("shipToPerson.id").withOperator(QCriteriaOperator.NOT_EQUALS).withOtherFieldName("billToPerson.id"))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
assertEquals(specialOrderId, queryOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// re-run that query using personIds from the order table //
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("shipToPerson.lastName").withOperator(QCriteriaOperator.EQUALS).withOtherFieldName("billToPerson.lastName"))
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("order.shipToPersonId").withOperator(QCriteriaOperator.NOT_EQUALS).withOtherFieldName("order.billToPersonId"))
|
||||||
|
);
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
assertEquals(specialOrderId, queryOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// re-run that query using personIds from the order table, but not specifying the table name //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("shipToPerson.lastName").withOperator(QCriteriaOperator.EQUALS).withOtherFieldName("billToPerson.lastName"))
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("shipToPersonId").withOperator(QCriteriaOperator.NOT_EQUALS).withOtherFieldName("billToPersonId"))
|
||||||
|
);
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "# of rows found by query");
|
||||||
|
assertEquals(specialOrderId, queryOutput.getRecords().get(0).getValueInteger("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testDuplicateAliases()
|
||||||
|
{
|
||||||
|
QInstance instance = TestUtils.defineInstance();
|
||||||
|
QueryInput queryInput = new QueryInput(instance, new QSession());
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withAlias("shipToPerson"),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withAlias("billToPerson"),
|
||||||
|
new QueryJoin("billToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true),
|
||||||
|
new QueryJoin("shipToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true) // w/o alias, should get exception here - dupe table.
|
||||||
|
));
|
||||||
|
assertThatThrownBy(() -> new QueryAction().execute(queryInput))
|
||||||
|
.hasRootCauseMessage("Duplicate table name or alias: personalIdCard");
|
||||||
|
|
||||||
|
queryInput.withQueryJoins(List.of(
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinShipToPerson")).withAlias("shipToPerson"),
|
||||||
|
new QueryJoin(instance.getJoin("orderJoinBillToPerson")).withAlias("billToPerson"),
|
||||||
|
new QueryJoin("shipToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("shipToPerson").withSelect(true), // dupe alias, should get exception here
|
||||||
|
new QueryJoin("billToPerson", TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("billToPerson").withSelect(true)
|
||||||
|
));
|
||||||
|
assertThatThrownBy(() -> new QueryAction().execute(queryInput))
|
||||||
|
.hasRootCauseMessage("Duplicate table name or alias: shipToPerson");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -41,6 +41,23 @@ INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, a
|
|||||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 30000, 99);
|
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 30000, 99);
|
||||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1, 1000000, 232);
|
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1, 1000000, 232);
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS personal_id_card;
|
||||||
|
CREATE TABLE personal_id_card
|
||||||
|
(
|
||||||
|
id INT AUTO_INCREMENT primary key ,
|
||||||
|
create_date TIMESTAMP DEFAULT now(),
|
||||||
|
modify_date TIMESTAMP DEFAULT now(),
|
||||||
|
person_id INTEGER,
|
||||||
|
id_number VARCHAR(250)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO personal_id_card (person_id, id_number) VALUES (1, '19800531');
|
||||||
|
INSERT INTO personal_id_card (person_id, id_number) VALUES (2, '19800515');
|
||||||
|
INSERT INTO personal_id_card (person_id, id_number) VALUES (3, '19760528');
|
||||||
|
INSERT INTO personal_id_card (person_id, id_number) VALUES (6, '123123123');
|
||||||
|
INSERT INTO personal_id_card (person_id, id_number) VALUES (null, '987987987');
|
||||||
|
INSERT INTO personal_id_card (person_id, id_number) VALUES (null, '456456456');
|
||||||
|
|
||||||
DROP TABLE IF EXISTS carrier;
|
DROP TABLE IF EXISTS carrier;
|
||||||
CREATE TABLE carrier
|
CREATE TABLE carrier
|
||||||
(
|
(
|
||||||
@ -61,3 +78,77 @@ INSERT INTO carrier (id, name, company_code, service_level) VALUES (8, 'USPS Sup
|
|||||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (9, 'USPS Super Fast', 'USPS', '0');
|
INSERT INTO carrier (id, name, company_code, service_level) VALUES (9, 'USPS Super Fast', 'USPS', '0');
|
||||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (10, 'DHL International', 'DHL', 'I');
|
INSERT INTO carrier (id, name, company_code, service_level) VALUES (10, 'DHL International', 'DHL', 'I');
|
||||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (11, 'GSO', 'GSO', 'G');
|
INSERT INTO carrier (id, name, company_code, service_level) VALUES (11, 'GSO', 'GSO', 'G');
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS order_line;
|
||||||
|
DROP TABLE IF EXISTS item;
|
||||||
|
DROP TABLE IF EXISTS `order`;
|
||||||
|
DROP TABLE IF EXISTS store;
|
||||||
|
|
||||||
|
CREATE TABLE store
|
||||||
|
(
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(80) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- define 3 stores
|
||||||
|
INSERT INTO store (id, name) VALUES (1, 'Q-Mart');
|
||||||
|
INSERT INTO store (id, name) VALUES (2, 'QQQ ''R'' Us');
|
||||||
|
INSERT INTO store (id, name) VALUES (3, 'QDepot');
|
||||||
|
|
||||||
|
CREATE TABLE item
|
||||||
|
(
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
sku VARCHAR(80) NOT NULL,
|
||||||
|
store_id INT NOT NULL REFERENCES store
|
||||||
|
);
|
||||||
|
|
||||||
|
-- three items for each store
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (1, 'QM-1', 1);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (2, 'QM-2', 1);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (3, 'QM-3', 1);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (4, 'QRU-1', 2);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (5, 'QRU-2', 2);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (6, 'QRU-3', 2);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (7, 'QD-1', 3);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (8, 'QD-2', 3);
|
||||||
|
INSERT INTO item (id, sku, store_id) VALUES (9, 'QD-3', 3);
|
||||||
|
|
||||||
|
CREATE TABLE `order`
|
||||||
|
(
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
store_id INT REFERENCES store,
|
||||||
|
bill_to_person_id INT,
|
||||||
|
ship_to_person_id INT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- variable orders
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (1, 1, 1, 1);
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (2, 1, 1, 2);
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (3, 1, 2, 3);
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (4, 2, 4, 5);
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (5, 2, 5, 4);
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (6, 3, 5, null);
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (7, 3, null, 5);
|
||||||
|
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (8, 3, null, 5);
|
||||||
|
|
||||||
|
CREATE TABLE order_line
|
||||||
|
(
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
order_id INT REFERENCES `order`,
|
||||||
|
sku VARCHAR(80),
|
||||||
|
store_id INT REFERENCES store, -- todo - as a challenge, if this field wasn't here, so we had to join through order...
|
||||||
|
quantity INT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- various lines
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (1, 'QM-1', 1, 10);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (1, 'QM-2', 1, 1);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (1, 'QM-3', 1, 1);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (2, 'QRU-1', 2, 1); -- this line has an item from a different store than its order.
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (3, 'QM-1', 1, 20);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (4, 'QRU-1', 2, 1);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (4, 'QRU-2', 2, 2);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (5, 'QRU-1', 2, 1);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (6, 'QD-1', 3, 1);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (7, 'QD-1', 3, 2);
|
||||||
|
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (8, 'QD-1', 3, 3);
|
||||||
|
Reference in New Issue
Block a user