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.ReportFormat;
|
||||
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.QFilterOrderBy;
|
||||
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());
|
||||
|
||||
@ -200,6 +201,8 @@ public class GenerateReportAction
|
||||
exportInput.setIncludeHeaderRow(reportView.getIncludeHeaderRow());
|
||||
exportInput.setReportOutputStream(reportInput.getReportOutputStream());
|
||||
|
||||
JoinsContext joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins());
|
||||
|
||||
List<QFieldMetaData> fields;
|
||||
if(CollectionUtils.nullSafeHasContents(reportView.getColumns()))
|
||||
{
|
||||
@ -212,7 +215,14 @@ public class GenerateReportAction
|
||||
}
|
||||
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()))
|
||||
{
|
||||
field.setLabel(column.getLabel());
|
||||
@ -278,6 +288,7 @@ public class GenerateReportAction
|
||||
queryInput.setRecordPipe(recordPipe);
|
||||
queryInput.setTableName(dataSource.getSourceTable());
|
||||
queryInput.setFilter(queryFilter);
|
||||
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
||||
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.QProcessMetaData;
|
||||
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.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
@ -365,6 +367,50 @@ public class QInstanceEnricher
|
||||
{
|
||||
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 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.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
@ -39,6 +40,8 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
private List<Aggregate> aggregates;
|
||||
private List<String> groupByFieldNames;
|
||||
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -192,4 +195,54 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
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;
|
||||
|
||||
|
||||
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.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
@ -35,6 +38,8 @@ public class CountInput extends AbstractTableActionInput
|
||||
{
|
||||
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.List;
|
||||
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.Logger;
|
||||
|
||||
@ -43,6 +44,8 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
private QCriteriaOperator operator;
|
||||
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(" ");
|
||||
if(CollectionUtils.nullSafeHasContents(values))
|
||||
{
|
||||
if(values.size() == 1)
|
||||
if(StringUtils.hasContent(otherFieldName))
|
||||
{
|
||||
rs.append(values.get(0));
|
||||
rs.append(otherFieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = 0;
|
||||
for(Serializable value : values)
|
||||
if(values.size() == 1)
|
||||
{
|
||||
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");
|
||||
break;
|
||||
if(index++ > 9)
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
@ -45,6 +47,8 @@ public class QueryInput extends AbstractTableActionInput
|
||||
private boolean shouldTranslatePossibleValues = false;
|
||||
private boolean shouldGenerateDisplayValues = false;
|
||||
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -241,4 +245,54 @@ public class QueryInput extends AbstractTableActionInput
|
||||
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
|
||||
{
|
||||
@ -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
|
||||
**
|
||||
|
@ -25,7 +25,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.joins;
|
||||
/*******************************************************************************
|
||||
** 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
|
||||
** records. Like Order -> OrderLine.
|
||||
** - 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_MANY,
|
||||
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
|
||||
{
|
||||
@ -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
|
||||
**
|
||||
|
@ -22,7 +22,10 @@
|
||||
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.QueryJoin;
|
||||
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
|
||||
{
|
||||
private String name;
|
||||
private String sourceTable;
|
||||
private QQueryFilter queryFilter;
|
||||
private String name;
|
||||
|
||||
private String sourceTable;
|
||||
private QQueryFilter queryFilter;
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
|
||||
|
||||
private QCodeReference staticDataSupplier;
|
||||
|
||||
@ -174,4 +180,54 @@ public class QReportDataSource
|
||||
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;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
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
|
||||
**
|
||||
@ -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
|
||||
**
|
||||
@ -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.QProcessMetaData;
|
||||
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.queues.SQSQueueProviderMetaData;
|
||||
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.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
@ -1344,11 +1345,35 @@ class QInstanceValidatorTest
|
||||
@Test
|
||||
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",
|
||||
"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",
|
||||
"unrecognized dataSourceName");
|
||||
|
||||
|
@ -27,7 +27,9 @@ import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
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.tables.aggregate.Aggregate;
|
||||
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.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.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.utils.CollectionUtils;
|
||||
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()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////
|
||||
@ -189,7 +275,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
||||
}
|
||||
for(QQueryFilter subFilter : filter.getSubFilters())
|
||||
{
|
||||
String subClause = makeWhereClause(table, subFilter, params);
|
||||
String subClause = makeWhereClause(instance, table, joinsContext, subFilter, params);
|
||||
if(StringUtils.hasContent(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<>();
|
||||
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());
|
||||
String column = getColumnName(field);
|
||||
QFieldMetaData field = fieldAndTableNameOrAlias.field();
|
||||
String column = escapeIdentifier(fieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(getColumnName(field));
|
||||
String clause = column;
|
||||
Integer expectedNoOfParams = null;
|
||||
switch(criterion.getOperator())
|
||||
@ -360,15 +448,29 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
||||
throw new IllegalArgumentException("Unexpected operator: " + criterion.getOperator());
|
||||
}
|
||||
}
|
||||
clauses.add("(" + clause + ")");
|
||||
|
||||
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() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
clauses.add("(" + clause + ")");
|
||||
|
||||
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<>();
|
||||
|
||||
@ -485,9 +587,11 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
||||
}
|
||||
else
|
||||
{
|
||||
QFieldMetaData field = table.getField(orderBy.getFieldName());
|
||||
String column = escapeIdentifier(getColumnName(field));
|
||||
clauses.add(column + " " + ascOrDesc);
|
||||
JoinsContext.FieldAndTableNameOrAlias otherFieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(orderBy.getFieldName());
|
||||
|
||||
QFieldMetaData field = otherFieldAndTableNameOrAlias.field();
|
||||
String column = getColumnName(field);
|
||||
clauses.add(escapeIdentifier(otherFieldAndTableNameOrAlias.tableNameOrAlias()) + "." + escapeIdentifier(column) + " " + ascOrDesc);
|
||||
}
|
||||
}
|
||||
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.AggregateOutput;
|
||||
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.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
@ -61,29 +62,30 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
||||
{
|
||||
try
|
||||
{
|
||||
QTableMetaData table = aggregateInput.getTable();
|
||||
String tableName = getTableName(table);
|
||||
QTableMetaData table = aggregateInput.getTable();
|
||||
|
||||
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)
|
||||
+ " FROM " + escapeIdentifier(tableName);
|
||||
+ " FROM " + fromClause;
|
||||
|
||||
QQueryFilter filter = aggregateInput.getFilter();
|
||||
List<Serializable> params = new ArrayList<>();
|
||||
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()))
|
||||
{
|
||||
sql += " GROUP BY " + makeGroupByClause(aggregateInput);
|
||||
sql += " GROUP BY " + makeGroupByClause(aggregateInput, joinsContext);
|
||||
}
|
||||
|
||||
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
|
||||
@ -105,13 +107,16 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
||||
int selectionIndex = 1;
|
||||
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);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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()))
|
||||
{
|
||||
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())
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -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())
|
||||
{
|
||||
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));
|
||||
|
@ -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.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.query.JoinsContext;
|
||||
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.module.rdbms.jdbc.QueryManager;
|
||||
@ -54,16 +55,18 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
||||
{
|
||||
try
|
||||
{
|
||||
QTableMetaData table = countInput.getTable();
|
||||
String tableName = getTableName(table);
|
||||
QTableMetaData table = countInput.getTable();
|
||||
|
||||
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();
|
||||
List<Serializable> params = new ArrayList<>();
|
||||
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
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
|
||||
import java.io.Serializable;
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
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.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.query.JoinsContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
@ -258,8 +260,9 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
|
||||
List<Serializable> params = new ArrayList<>();
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
|
||||
String tableName = getTableName(table);
|
||||
String whereClause = makeWhereClause(table, filter, params);
|
||||
String tableName = getTableName(table);
|
||||
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?
|
||||
String sql = "DELETE FROM "
|
||||
|
@ -34,10 +34,13 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
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.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.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;
|
||||
@ -64,35 +67,34 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
||||
try
|
||||
{
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
String tableName = getTableName(table);
|
||||
String tableName = queryInput.getTableName();
|
||||
|
||||
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values());
|
||||
String columns = fieldList.stream()
|
||||
.map(this::getColumnName)
|
||||
.collect(Collectors.joining(", "));
|
||||
StringBuilder sql = new StringBuilder("SELECT ").append(makeSelectClause(queryInput.getInstance(), tableName, queryInput.getQueryJoins()));
|
||||
|
||||
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();
|
||||
List<Serializable> params = new ArrayList<>();
|
||||
|
||||
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()))
|
||||
{
|
||||
sql += " ORDER BY " + makeOrderByClause(table, filter.getOrderBys());
|
||||
sql.append(" ORDER BY ").append(makeOrderByClause(table, filter.getOrderBys(), joinsContext));
|
||||
}
|
||||
|
||||
if(queryInput.getLimit() != null)
|
||||
{
|
||||
sql += " LIMIT " + queryInput.getLimit();
|
||||
sql.append(" LIMIT ").append(queryInput.getLimit());
|
||||
|
||||
if(queryInput.getSkip() != null)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
{
|
||||
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) ->
|
||||
{
|
||||
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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||
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 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.addBackend(defineBackend());
|
||||
qInstance.addTable(defineTablePerson());
|
||||
qInstance.addTable(defineTablePersonalIdCard());
|
||||
qInstance.addJoin(defineJoinPersonAndPersonalIdCard());
|
||||
addOmsTablesAndJoins(qInstance);
|
||||
qInstance.setAuthentication(defineAuthentication());
|
||||
return (qInstance);
|
||||
}
|
||||
@ -121,9 +133,9 @@ public class TestUtils
|
||||
public static QTableMetaData defineTablePerson()
|
||||
{
|
||||
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")
|
||||
.withBackendName(defineBackend().getName())
|
||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date"))
|
||||
@ -139,4 +151,136 @@ public class TestUtils
|
||||
.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.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
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.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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 com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
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.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
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.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -60,7 +63,7 @@ public class RDBMSCountActionTest extends RDBMSActionTest
|
||||
{
|
||||
CountInput countInput = initCountRequest();
|
||||
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)))
|
||||
);
|
||||
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)))
|
||||
);
|
||||
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.setInstance(TestUtils.defineInstance());
|
||||
countInput.setSession(new QSession());
|
||||
countInput.setTableName(TestUtils.defineTablePerson().getName());
|
||||
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.concurrent.atomic.AtomicInteger;
|
||||
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.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.query.QCriteriaOperator;
|
||||
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.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
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.Test;
|
||||
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();
|
||||
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)))
|
||||
);
|
||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||
Assertions.assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||
Assertions.assertEquals(email, queryOutput.getRecords().get(0).getValueString("email"), "Should find expected email address");
|
||||
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||
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)))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -130,7 +136,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(2, 4)))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -150,7 +156,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(2, 3, 4)))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -170,7 +176,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of("darin")))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -190,7 +196,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of("kelkhoff")))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -210,7 +216,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of("gmail.com")))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -230,7 +236,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of("darin")))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -250,7 +256,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of("kelkhoff")))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -270,7 +276,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of("gmail.com")))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -290,7 +296,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(3)))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -310,7 +316,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(2)))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -330,7 +336,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(3)))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -350,7 +356,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(4)))
|
||||
);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -369,7 +375,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withOperator(QCriteriaOperator.IS_BLANK)
|
||||
));
|
||||
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");
|
||||
}
|
||||
|
||||
@ -388,7 +394,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withOperator(QCriteriaOperator.IS_NOT_BLANK)
|
||||
));
|
||||
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");
|
||||
}
|
||||
|
||||
@ -408,7 +414,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(2, 4))
|
||||
));
|
||||
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");
|
||||
}
|
||||
|
||||
@ -428,7 +434,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
.withValues(List.of(2, 4))
|
||||
));
|
||||
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");
|
||||
}
|
||||
|
||||
@ -441,7 +447,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setInstance(TestUtils.defineInstance());
|
||||
queryInput.setTableName(TestUtils.defineTablePerson().getName());
|
||||
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
queryInput.setSession(new QSession());
|
||||
return queryInput;
|
||||
}
|
||||
@ -459,7 +465,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
QueryInput queryInput = initQueryRequest();
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
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())
|
||||
{
|
||||
@ -479,7 +485,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
{
|
||||
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
|
||||
insertInput.setSession(new QSession());
|
||||
insertInput.setTableName(TestUtils.defineTablePerson().getName());
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
|
||||
InsertAction insertAction = new InsertAction();
|
||||
QBackendTransaction transaction = insertAction.openTransaction(insertInput);
|
||||
@ -493,12 +499,12 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
|
||||
QueryInput queryInput = initQueryRequest();
|
||||
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.setTransaction(transaction);
|
||||
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();
|
||||
}
|
||||
@ -514,11 +520,11 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
QueryInput queryInput = initQueryRequest();
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IN, List.of())));
|
||||
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())));
|
||||
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")))
|
||||
);
|
||||
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("Tim"));
|
||||
}
|
||||
@ -564,7 +570,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
))
|
||||
);
|
||||
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("Darin") && r.getValueString("lastName").equals("Kelkhoff"));
|
||||
}
|
||||
@ -592,8 +598,322 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
))
|
||||
);
|
||||
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"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@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 (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;
|
||||
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 (10, 'DHL International', 'DHL', 'I');
|
||||
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