mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-21 22:48:44 +00:00
Compare commits
15 Commits
snapshot-f
...
version-0.
Author | SHA1 | Date | |
---|---|---|---|
6702c06ed0 | |||
9dfbd839c8 | |||
724d5779cc | |||
1fef376e65 | |||
d3417a0652 | |||
053d5f1058 | |||
47e27d5ffc | |||
59a70a4cb7 | |||
fea757c46d | |||
9a65ea81b2 | |||
494ec00b84 | |||
51eb7d89be | |||
0b5e97d596 | |||
2609bc801c | |||
36307dba24 |
@ -7,8 +7,6 @@ fi
|
|||||||
|
|
||||||
if [ "$CIRCLE_BRANCH" == "dev" ] || [ "$CIRCLE_BRANCH" == "staging" ] || [ "$CIRCLE_BRANCH" == "main" ] || [ \! -z $(echo "$CIRCLE_TAG" | grep "^version-") ]; then
|
if [ "$CIRCLE_BRANCH" == "dev" ] || [ "$CIRCLE_BRANCH" == "staging" ] || [ "$CIRCLE_BRANCH" == "main" ] || [ \! -z $(echo "$CIRCLE_TAG" | grep "^version-") ]; then
|
||||||
echo "On a primary branch or tag [${CIRCLE_BRANCH}${CIRCLE_TAG}] - will not edit the pom version.";
|
echo "On a primary branch or tag [${CIRCLE_BRANCH}${CIRCLE_TAG}] - will not edit the pom version.";
|
||||||
echo "(or do we need to do this for dev...?)"
|
|
||||||
echo "(maybe we do this if the current version is a SNAPSHOT?)"
|
|
||||||
exit 0;
|
exit 0;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -19,9 +17,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
POM=$(dirname $0)/../pom.xml
|
POM=$(dirname $0)/../pom.xml
|
||||||
UNIQ=$(date +%Y%m%d-%H%M%S)-$(git rev-parse --short HEAD)
|
|
||||||
SLUG="${SLUG}-${UNIQ}"
|
|
||||||
|
|
||||||
echo "Updating $POM <revision> to: $SLUG"
|
echo "Updating $POM <revision> to: $SLUG-SNAPSHOT"
|
||||||
sed -i "s/<revision>.*/<revision>$SLUG<\/revision>/" $POM
|
sed -i "s/<revision>.*/<revision>$SLUG-SNAPSHOT<\/revision>/" $POM
|
||||||
git diff $POM
|
git diff $POM
|
||||||
|
@ -48,5 +48,3 @@ GNU Affero General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,9 +33,6 @@ If the {link-table} has a `POST_QUERY_CUSTOMIZER` defined, then after records ar
|
|||||||
* `table` - *String, Required* - Name of the table being queried against.
|
* `table` - *String, Required* - Name of the table being queried against.
|
||||||
* `filter` - *<<QQueryFilter>> object* - Specification for what records should be returned, based on *<<QFilterCriteria>>* objects, and how they should be sorted, based on *<<QFilterOrderBy>>* objects.
|
* `filter` - *<<QQueryFilter>> object* - Specification for what records should be returned, based on *<<QFilterCriteria>>* objects, and how they should be sorted, based on *<<QFilterOrderBy>>* objects.
|
||||||
If a `filter` is not given, then all rows in the table will be returned by the query.
|
If a `filter` is not given, then all rows in the table will be returned by the query.
|
||||||
* `skip` - *Integer* - Optional number of records to be skipped at the beginning of the result set.
|
|
||||||
e.g., for implementing pagination.
|
|
||||||
* `limit` - *Integer* - Optional maximum number of records to be returned by the query.
|
|
||||||
* `transaction` - *QBackendTransaction object* - Optional transaction object.
|
* `transaction` - *QBackendTransaction object* - Optional transaction object.
|
||||||
** Behavior for this object is backend-dependant.
|
** Behavior for this object is backend-dependant.
|
||||||
In an RDBMS backend, this object is generally needed if you want your query to see data that may have been modified within the same transaction.
|
In an RDBMS backend, this object is generally needed if you want your query to see data that may have been modified within the same transaction.
|
||||||
@ -55,6 +52,14 @@ But if running a query to provide data as part of a process, then this can gener
|
|||||||
* `shouldMaskPassword` - *boolean, default: true* - Controls whether or not fields with `type` = `PASSWORD` should be masked, or if their actual values should be returned.
|
* `shouldMaskPassword` - *boolean, default: true* - Controls whether or not fields with `type` = `PASSWORD` should be masked, or if their actual values should be returned.
|
||||||
* `queryJoins` - *List of <<QueryJoin>> objects* - Optional list of tables to be joined with the main table being queried.
|
* `queryJoins` - *List of <<QueryJoin>> objects* - Optional list of tables to be joined with the main table being queried.
|
||||||
See QueryJoin below for further details.
|
See QueryJoin below for further details.
|
||||||
|
* `fieldNamesToInclude` - *Set of String* - Optional set of field names to be included in the records.
|
||||||
|
** Fields from a queryJoin must be prefixed by the join table's name or alias, and a period.
|
||||||
|
Field names from the table being queried should not have any sort of prefix.
|
||||||
|
** A `null` set here (default) means to include all fields from the table and any queryJoins set as select=true.
|
||||||
|
** An empty set will cause an error, as well any unrecognized field names.
|
||||||
|
** `QueryAction` will validate the set of field names, and throw an exception if any unrecognized names are given.
|
||||||
|
** _Note that this is an optional feature, which some backend modules may not implement.
|
||||||
|
Meaning, they would always return all fields._
|
||||||
|
|
||||||
==== QQueryFilter
|
==== QQueryFilter
|
||||||
A key component of *<<QueryInput>>*, a *QQueryFilter* defines both what records should be included in a query's results (e.g., an SQL `WHERE`), as well as how those results should be sorted (SQL `ORDER BY`).
|
A key component of *<<QueryInput>>*, a *QQueryFilter* defines both what records should be included in a query's results (e.g., an SQL `WHERE`), as well as how those results should be sorted (SQL `ORDER BY`).
|
||||||
@ -68,6 +73,9 @@ In general, multiple *orderBys* can be given (depending on backend implementatio
|
|||||||
** Each *subFilter* can include its own additional *subFilters*.
|
** Each *subFilter* can include its own additional *subFilters*.
|
||||||
** Each *subFilter* can specify a different *booleanOperator*.
|
** Each *subFilter* can specify a different *booleanOperator*.
|
||||||
** For example, consider the following *QQueryFilter*, that uses two *subFilters*, and a mix of *booleanOperators*
|
** For example, consider the following *QQueryFilter*, that uses two *subFilters*, and a mix of *booleanOperators*
|
||||||
|
* `skip` - *Integer* - Optional number of records to be skipped at the beginning of the result set.
|
||||||
|
e.g., for implementing pagination.
|
||||||
|
* `limit` - *Integer* - Optional maximum number of records to be returned by the query.
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
|
2
pom.xml
2
pom.xml
@ -46,7 +46,7 @@
|
|||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.21.0-SNAPSHOT</revision>
|
<revision>0.21.0</revision>
|
||||||
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
@ -24,10 +24,12 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
@ -143,4 +145,19 @@ public interface RecordCustomizerUtilityInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
default Map<Serializable, QRecord> getOldRecordMap(List<QRecord> oldRecordList, UpdateInput updateInput)
|
||||||
|
{
|
||||||
|
Map<Serializable, QRecord> oldRecordMap = new HashMap<>();
|
||||||
|
for(QRecord qRecord : oldRecordList)
|
||||||
|
{
|
||||||
|
oldRecordMap.put(qRecord.getValue(updateInput.getTable().getPrimaryKeyField()), qRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (oldRecordMap);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.DataSourceQueryInputCustomizer;
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.DataSourceQueryInputCustomizer;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportViewCustomizer;
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportViewCustomizer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
@ -302,10 +303,19 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
JoinsContext joinsContext = null;
|
JoinsContext joinsContext = null;
|
||||||
if(dataSource != null)
|
if(dataSource != null)
|
||||||
{
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// count records, if applicable, from the data source - for populating into the //
|
||||||
|
// countByDataSource map, as well as for checking if too many rows (e.g., for excel) //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
countDataSourceRecords(reportInput, dataSource, reportFormat);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if there's a source table, set up a joins context, to use below for looking up fields //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(StringUtils.hasContent(dataSource.getSourceTable()))
|
if(StringUtils.hasContent(dataSource.getSourceTable()))
|
||||||
{
|
{
|
||||||
joinsContext = new JoinsContext(QContext.getQInstance(), dataSource.getSourceTable(), cloneDataSourceQueryJoins(dataSource), dataSource.getQueryFilter() == null ? null : dataSource.getQueryFilter().clone());
|
QQueryFilter queryFilter = dataSource.getQueryFilter() == null ? new QQueryFilter() : dataSource.getQueryFilter().clone();
|
||||||
countDataSourceRecords(reportInput, dataSource, reportFormat);
|
joinsContext = new JoinsContext(QContext.getQInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,6 +339,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
field.setName(column.getName());
|
field.setName(column.getName());
|
||||||
if(StringUtils.hasContent(column.getLabel()))
|
if(StringUtils.hasContent(column.getLabel()))
|
||||||
{
|
{
|
||||||
|
|
||||||
field.setLabel(column.getLabel());
|
field.setLabel(column.getLabel());
|
||||||
}
|
}
|
||||||
fields.add(field);
|
fields.add(field);
|
||||||
@ -346,23 +357,33 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void countDataSourceRecords(ReportInput reportInput, QReportDataSource dataSource, ReportFormat reportFormat) throws QException
|
private void countDataSourceRecords(ReportInput reportInput, QReportDataSource dataSource, ReportFormat reportFormat) throws QException
|
||||||
{
|
{
|
||||||
QQueryFilter queryFilter = dataSource.getQueryFilter() == null ? new QQueryFilter() : dataSource.getQueryFilter().clone();
|
Integer count = null;
|
||||||
setInputValuesInQueryFilter(reportInput, queryFilter);
|
if(dataSource.getCustomRecordSource() != null)
|
||||||
|
|
||||||
CountInput countInput = new CountInput();
|
|
||||||
countInput.setTableName(dataSource.getSourceTable());
|
|
||||||
countInput.setFilter(queryFilter);
|
|
||||||
countInput.setQueryJoins(cloneDataSourceQueryJoins(dataSource));
|
|
||||||
CountOutput countOutput = new CountAction().execute(countInput);
|
|
||||||
|
|
||||||
if(countOutput.getCount() != null)
|
|
||||||
{
|
{
|
||||||
countByDataSource.put(dataSource.getName(), countOutput.getCount());
|
// todo - add `count` method to interface?
|
||||||
|
}
|
||||||
|
else if(StringUtils.hasContent(dataSource.getSourceTable()))
|
||||||
|
{
|
||||||
|
QQueryFilter queryFilter = dataSource.getQueryFilter() == null ? new QQueryFilter() : dataSource.getQueryFilter().clone();
|
||||||
|
setInputValuesInQueryFilter(reportInput, queryFilter);
|
||||||
|
|
||||||
if(reportFormat.getMaxRows() != null && countOutput.getCount() > reportFormat.getMaxRows())
|
CountInput countInput = new CountInput();
|
||||||
|
countInput.setTableName(dataSource.getSourceTable());
|
||||||
|
countInput.setFilter(queryFilter);
|
||||||
|
countInput.setQueryJoins(cloneDataSourceQueryJoins(dataSource));
|
||||||
|
CountOutput countOutput = new CountAction().execute(countInput);
|
||||||
|
|
||||||
|
count = countOutput.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(count != null)
|
||||||
|
{
|
||||||
|
countByDataSource.put(dataSource.getName(), count);
|
||||||
|
|
||||||
|
if(reportFormat.getMaxRows() != null && count > reportFormat.getMaxRows())
|
||||||
{
|
{
|
||||||
throw (new QUserFacingException("The requested report would include more rows ("
|
throw (new QUserFacingException("The requested report would include more rows ("
|
||||||
+ String.format("%,d", countOutput.getCount()) + ") than the maximum allowed ("
|
+ String.format("%,d", count) + ") than the maximum allowed ("
|
||||||
+ String.format("%,d", reportFormat.getMaxRows()) + ") for the selected file format (" + reportFormat + ")."));
|
+ String.format("%,d", reportFormat.getMaxRows()) + ") for the selected file format (" + reportFormat + ")."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -423,13 +444,19 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
String tableLabel = ObjectUtils.tryElse(() -> QContext.getQInstance().getTable(dataSource.getSourceTable()).getLabel(), Objects.requireNonNullElse(dataSource.getSourceTable(), ""));
|
String tableLabel = ObjectUtils.tryElse(() -> QContext.getQInstance().getTable(dataSource.getSourceTable()).getLabel(), Objects.requireNonNullElse(dataSource.getSourceTable(), ""));
|
||||||
AtomicInteger consumedCount = new AtomicInteger(0);
|
AtomicInteger consumedCount = new AtomicInteger(0);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// run a record pipe loop, over the query for this data source //
|
// run a record pipe loop, over the query (or other data-supplier/source) for this data source //
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
RecordPipe recordPipe = new BufferedRecordPipe(1000);
|
RecordPipe recordPipe = new BufferedRecordPipe(1000);
|
||||||
new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) ->
|
new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) ->
|
||||||
{
|
{
|
||||||
if(dataSource.getSourceTable() != null)
|
if(dataSource.getCustomRecordSource() != null)
|
||||||
|
{
|
||||||
|
ReportCustomRecordSourceInterface recordSource = QCodeLoader.getAdHoc(ReportCustomRecordSourceInterface.class, dataSource.getCustomRecordSource());
|
||||||
|
recordSource.execute(reportInput, dataSource, recordPipe);
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
else if(dataSource.getSourceTable() != null)
|
||||||
{
|
{
|
||||||
QQueryFilter queryFilter = dataSource.getQueryFilter() == null ? new QQueryFilter() : dataSource.getQueryFilter().clone();
|
QQueryFilter queryFilter = dataSource.getQueryFilter() == null ? new QQueryFilter() : dataSource.getQueryFilter().clone();
|
||||||
setInputValuesInQueryFilter(reportInput, queryFilter);
|
setInputValuesInQueryFilter(reportInput, queryFilter);
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. 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.actions.reporting.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Interface to be implemented to do a custom source of data for a report
|
||||||
|
** (instead of just a query against a table).
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface ReportCustomRecordSourceInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** Given the report input, put records into the pipe, for the report.
|
||||||
|
***************************************************************************/
|
||||||
|
void execute(ReportInput reportInput, QReportDataSource reportDataSource, RecordPipe recordPipe) throws QException;
|
||||||
|
|
||||||
|
}
|
@ -124,10 +124,11 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
private Writer activeSheetWriter = null;
|
private Writer activeSheetWriter = null;
|
||||||
private StreamedSheetWriter sheetWriter = null;
|
private StreamedSheetWriter sheetWriter = null;
|
||||||
|
|
||||||
private QReportView currentView = null;
|
private QReportView currentView = null;
|
||||||
private Map<String, List<QFieldMetaData>> fieldsPerView = new HashMap<>();
|
private Map<String, List<QFieldMetaData>> fieldsPerView = new HashMap<>();
|
||||||
private Map<String, Integer> rowsPerView = new HashMap<>();
|
private Map<String, Integer> rowsPerView = new HashMap<>();
|
||||||
private Map<String, String> labelViewsByName = new HashMap<>();
|
private Map<String, String> labelViewsByName = new HashMap<>();
|
||||||
|
private Map<String, String> sheetReferenceByViewName = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -180,6 +181,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
String sheetReference = sheet.getPackagePart().getPartName().getName().substring(1);
|
String sheetReference = sheet.getPackagePart().getPartName().getName().substring(1);
|
||||||
sheetMapByExcelReference.put(sheetReference, sheet);
|
sheetMapByExcelReference.put(sheetReference, sheet);
|
||||||
sheetMapByViewName.put(view.getName(), sheet);
|
sheetMapByViewName.put(view.getName(), sheet);
|
||||||
|
sheetReferenceByViewName.put(view.getName(), sheetReference);
|
||||||
sheetCounter++;
|
sheetCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,7 +448,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
|||||||
// - with a new output stream writer //
|
// - with a new output stream writer //
|
||||||
// - and with a SpreadsheetWriter //
|
// - and with a SpreadsheetWriter //
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
zipOutputStream.putNextEntry(new ZipEntry("xl/worksheets/sheet" + this.sheetIndex++ + ".xml"));
|
zipOutputStream.putNextEntry(new ZipEntry(sheetReferenceByViewName.get(view.getName())));
|
||||||
activeSheetWriter = new OutputStreamWriter(zipOutputStream);
|
activeSheetWriter = new OutputStreamWriter(zipOutputStream);
|
||||||
sheetWriter = new StreamedSheetWriter(activeSheetWriter);
|
sheetWriter = new StreamedSheetWriter(activeSheetWriter);
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import java.io.Serializable;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -50,6 +51,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperat
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
@ -64,6 +66,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
|||||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -101,6 +104,8 @@ public class QueryAction
|
|||||||
throw (new QException("A table named [" + queryInput.getTableName() + "] was not found in the active QInstance"));
|
throw (new QException("A table named [" + queryInput.getTableName() + "] was not found in the active QInstance"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateFieldNamesToInclude(queryInput);
|
||||||
|
|
||||||
QBackendMetaData backend = queryInput.getBackend();
|
QBackendMetaData backend = queryInput.getBackend();
|
||||||
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||||
this.queryInput = queryInput;
|
this.queryInput = queryInput;
|
||||||
@ -158,6 +163,109 @@ public class QueryAction
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** if QueryInput contains a set of FieldNamesToInclude, then validate that
|
||||||
|
** those are known field names in the table being queried, or a selected
|
||||||
|
** queryJoin.
|
||||||
|
***************************************************************************/
|
||||||
|
static void validateFieldNamesToInclude(QueryInput queryInput) throws QException
|
||||||
|
{
|
||||||
|
Set<String> fieldNamesToInclude = queryInput.getFieldNamesToInclude();
|
||||||
|
if(fieldNamesToInclude == null)
|
||||||
|
{
|
||||||
|
////////////////////////////////
|
||||||
|
// null set means select all. //
|
||||||
|
////////////////////////////////
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fieldNamesToInclude.isEmpty())
|
||||||
|
{
|
||||||
|
/////////////////////////////////////
|
||||||
|
// empty set, however, is an error //
|
||||||
|
/////////////////////////////////////
|
||||||
|
throw (new QException("An empty set of fieldNamesToInclude was given as queryInput, which is not allowed."));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> unrecognizedFieldNames = new ArrayList<>();
|
||||||
|
Map<String, QTableMetaData> selectedQueryJoins = null;
|
||||||
|
for(String fieldName : fieldNamesToInclude)
|
||||||
|
{
|
||||||
|
if(fieldName.contains("."))
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// handle names with dots - fields from joins //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
String[] parts = fieldName.split("\\.");
|
||||||
|
if(parts.length != 2)
|
||||||
|
{
|
||||||
|
unrecognizedFieldNames.add(fieldName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String tableOrAlias = parts[0];
|
||||||
|
String fieldNamePart = parts[1];
|
||||||
|
|
||||||
|
////////////////////////////////////////////
|
||||||
|
// build map of queryJoins being selected //
|
||||||
|
////////////////////////////////////////////
|
||||||
|
if(selectedQueryJoins == null)
|
||||||
|
{
|
||||||
|
selectedQueryJoins = new HashMap<>();
|
||||||
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryInput.getQueryJoins()))
|
||||||
|
{
|
||||||
|
if(queryJoin.getSelect())
|
||||||
|
{
|
||||||
|
String joinTableOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
|
QTableMetaData joinTable = QContext.getQInstance().getTable(queryJoin.getJoinTable());
|
||||||
|
if(joinTable != null)
|
||||||
|
{
|
||||||
|
selectedQueryJoins.put(joinTableOrAlias, joinTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!selectedQueryJoins.containsKey(tableOrAlias))
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////
|
||||||
|
// unrecognized tableOrAlias is an error //
|
||||||
|
///////////////////////////////////////////
|
||||||
|
unrecognizedFieldNames.add(fieldName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTableMetaData joinTable = selectedQueryJoins.get(tableOrAlias);
|
||||||
|
if(!joinTable.getFields().containsKey(fieldNamePart))
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// unrecognized field within the join table is an error //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
unrecognizedFieldNames.add(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// non-join fields - just ensure field name is in table's fields map //
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
if(!queryInput.getTable().getFields().containsKey(fieldName))
|
||||||
|
{
|
||||||
|
unrecognizedFieldNames.add(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!unrecognizedFieldNames.isEmpty())
|
||||||
|
{
|
||||||
|
throw (new QException("QueryInput contained " + unrecognizedFieldNames.size() + " unrecognized field name" + StringUtils.plural(unrecognizedFieldNames) + ": " + StringUtils.join(",", unrecognizedFieldNames)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** shorthand way to call for the most common use-case, when you just want the
|
** shorthand way to call for the most common use-case, when you just want the
|
||||||
** records to be returned, and you just want to pass in a table name and filter.
|
** records to be returned, and you just want to pass in a table name and filter.
|
||||||
|
@ -44,6 +44,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
|
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||||
@ -1660,9 +1661,12 @@ public class QInstanceValidator
|
|||||||
|
|
||||||
String dataSourceErrorPrefix = "Report " + reportName + " data source " + dataSource.getName() + " ";
|
String dataSourceErrorPrefix = "Report " + reportName + " data source " + dataSource.getName() + " ";
|
||||||
|
|
||||||
|
boolean hasASource = false;
|
||||||
|
|
||||||
if(StringUtils.hasContent(dataSource.getSourceTable()))
|
if(StringUtils.hasContent(dataSource.getSourceTable()))
|
||||||
{
|
{
|
||||||
assertCondition(dataSource.getStaticDataSupplier() == null, dataSourceErrorPrefix + "has both a sourceTable and a staticDataSupplier (exactly 1 is required).");
|
hasASource = true;
|
||||||
|
assertCondition(dataSource.getStaticDataSupplier() == null, dataSourceErrorPrefix + "has both a sourceTable and a staticDataSupplier (not compatible together).");
|
||||||
if(assertCondition(qInstance.getTable(dataSource.getSourceTable()) != null, dataSourceErrorPrefix + "source table " + dataSource.getSourceTable() + " is not a table in this instance."))
|
if(assertCondition(qInstance.getTable(dataSource.getSourceTable()) != null, dataSourceErrorPrefix + "source table " + dataSource.getSourceTable() + " is not a table in this instance."))
|
||||||
{
|
{
|
||||||
if(dataSource.getQueryFilter() != null)
|
if(dataSource.getQueryFilter() != null)
|
||||||
@ -1671,14 +1675,21 @@ public class QInstanceValidator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(dataSource.getStaticDataSupplier() != null)
|
|
||||||
|
if(dataSource.getStaticDataSupplier() != null)
|
||||||
{
|
{
|
||||||
|
assertCondition(dataSource.getCustomRecordSource() == null, dataSourceErrorPrefix + "has both a staticDataSupplier and a customRecordSource (not compatible together).");
|
||||||
|
hasASource = true;
|
||||||
validateSimpleCodeReference(dataSourceErrorPrefix, dataSource.getStaticDataSupplier(), Supplier.class);
|
validateSimpleCodeReference(dataSourceErrorPrefix, dataSource.getStaticDataSupplier(), Supplier.class);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if(dataSource.getCustomRecordSource() != null)
|
||||||
{
|
{
|
||||||
errors.add(dataSourceErrorPrefix + "does not have a sourceTable or a staticDataSupplier (exactly 1 is required).");
|
hasASource = true;
|
||||||
|
validateSimpleCodeReference(dataSourceErrorPrefix, dataSource.getCustomRecordSource(), ReportCustomRecordSourceInterface.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertCondition(hasASource, dataSourceErrorPrefix + "does not have a sourceTable, customRecordSource, or a staticDataSupplier.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,14 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
|
|||||||
private List<QueryJoin> queryJoins = null;
|
private List<QueryJoin> queryJoins = null;
|
||||||
private boolean selectDistinct = false;
|
private boolean selectDistinct = false;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if this set is null, then the default (all fields) should be included //
|
||||||
|
// if it's an empty set, that should throw an error //
|
||||||
|
// or if there are any fields in it that aren't valid fields on the table, //
|
||||||
|
// or in a selected queryJoin. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
private Set<String> fieldNamesToInclude;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// if you say you want to includeAssociations, you can limit which ones by passing them in associationNamesToInclude. //
|
// if you say you want to includeAssociations, you can limit which ones by passing them in associationNamesToInclude. //
|
||||||
// if you leave it null, you get all associations defined on the table. if you pass it as empty, you get none. //
|
// if you leave it null, you get all associations defined on the table. if you pass it as empty, you get none. //
|
||||||
@ -686,4 +694,35 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
|
|||||||
return (queryHints.contains(queryHint));
|
return (queryHints.contains(queryHint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for fieldNamesToInclude
|
||||||
|
*******************************************************************************/
|
||||||
|
public Set<String> getFieldNamesToInclude()
|
||||||
|
{
|
||||||
|
return (this.fieldNamesToInclude);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for fieldNamesToInclude
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFieldNamesToInclude(Set<String> fieldNamesToInclude)
|
||||||
|
{
|
||||||
|
this.fieldNamesToInclude = fieldNamesToInclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for fieldNamesToInclude
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryInput withFieldNamesToInclude(Set<String> fieldNamesToInclude)
|
||||||
|
{
|
||||||
|
this.fieldNamesToInclude = fieldNamesToInclude;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,10 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
|
|||||||
{
|
{
|
||||||
private List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks = new ArrayList<>();
|
private List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks = new ArrayList<>();
|
||||||
|
|
||||||
private Map<String, Serializable> styleOverrides = new HashMap<>();
|
private Layout layout;
|
||||||
|
private Map<String, Serializable> styleOverrides = new HashMap<>();
|
||||||
private Layout layout;
|
private String overlayHtml;
|
||||||
|
private Map<String, Serializable> overlayStyleOverrides = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -218,4 +219,91 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for overlayHtml
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getOverlayHtml()
|
||||||
|
{
|
||||||
|
return (this.overlayHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for overlayHtml
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOverlayHtml(String overlayHtml)
|
||||||
|
{
|
||||||
|
this.overlayHtml = overlayHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for overlayHtml
|
||||||
|
*******************************************************************************/
|
||||||
|
public CompositeWidgetData withOverlayHtml(String overlayHtml)
|
||||||
|
{
|
||||||
|
this.overlayHtml = overlayHtml;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for overlayStyleOverrides
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Serializable> getOverlayStyleOverrides()
|
||||||
|
{
|
||||||
|
return (this.overlayStyleOverrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for overlayStyleOverrides
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOverlayStyleOverrides(Map<String, Serializable> overlayStyleOverrides)
|
||||||
|
{
|
||||||
|
this.overlayStyleOverrides = overlayStyleOverrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for overlayStyleOverrides
|
||||||
|
*******************************************************************************/
|
||||||
|
public CompositeWidgetData withOverlayStyleOverrides(Map<String, Serializable> overlayStyleOverrides)
|
||||||
|
{
|
||||||
|
this.overlayStyleOverrides = overlayStyleOverrides;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public CompositeWidgetData withOverlayStyleOverride(String key, Serializable value)
|
||||||
|
{
|
||||||
|
addOverlayStyleOverride(key, value);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void addOverlayStyleOverride(String key, Serializable value)
|
||||||
|
{
|
||||||
|
if(this.overlayStyleOverrides == null)
|
||||||
|
{
|
||||||
|
this.overlayStyleOverrides = new HashMap<>();
|
||||||
|
}
|
||||||
|
this.overlayStyleOverrides.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.CompositeWidgetData;
|
||||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.QWidgetData;
|
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.QWidgetData;
|
||||||
|
|
||||||
|
|
||||||
@ -203,6 +204,19 @@ public abstract class AbstractBlockWidgetData<
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for tooltip
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T withTooltip(CompositeWidgetData data)
|
||||||
|
{
|
||||||
|
this.tooltip = new BlockTooltip(data);
|
||||||
|
return (T) (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -398,6 +412,7 @@ public abstract class AbstractBlockWidgetData<
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for blockId
|
** Getter for blockId
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -428,5 +443,4 @@ public abstract class AbstractBlockWidgetData<
|
|||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,14 +22,18 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.CompositeWidgetData;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** A tooltip used within a (widget) block.
|
** A tooltip used within a (widget) block.
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class BlockTooltip
|
public class BlockTooltip
|
||||||
{
|
{
|
||||||
private String title;
|
private CompositeWidgetData blockData;
|
||||||
private Placement placement = Placement.BOTTOM;
|
private String title;
|
||||||
|
private Placement placement = Placement.BOTTOM;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -62,6 +66,17 @@ public class BlockTooltip
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public BlockTooltip(CompositeWidgetData blockData)
|
||||||
|
{
|
||||||
|
this.blockData = blockData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for title
|
** Getter for title
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -122,4 +137,35 @@ public class BlockTooltip
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for blockData
|
||||||
|
*******************************************************************************/
|
||||||
|
public CompositeWidgetData getBlockData()
|
||||||
|
{
|
||||||
|
return (this.blockData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for blockData
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setBlockData(CompositeWidgetData blockData)
|
||||||
|
{
|
||||||
|
this.blockData = blockData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for blockData
|
||||||
|
*******************************************************************************/
|
||||||
|
public BlockTooltip withBlockData(CompositeWidgetData blockData)
|
||||||
|
{
|
||||||
|
this.blockData = blockData;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Meta-data definition of a source of data for a report (e.g., a table and query
|
** Meta-data definition of a source of data for a report (e.g., a table and query
|
||||||
** filter or custom-code reference).
|
** filter or custom-code reference).
|
||||||
|
**
|
||||||
|
** Runs in 3 modes:
|
||||||
|
**
|
||||||
|
** - If a customRecordSource is specified, then that code is executed to get the records.
|
||||||
|
** - else, if a sourceTable is specified, then the corresponding queryFilter
|
||||||
|
** (optionally along with queryJoins and queryInputCustomizer) is used.
|
||||||
|
** - else a staticDataSupplier is used.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QReportDataSource
|
public class QReportDataSource
|
||||||
{
|
{
|
||||||
@ -44,6 +51,7 @@ public class QReportDataSource
|
|||||||
|
|
||||||
private QCodeReference queryInputCustomizer;
|
private QCodeReference queryInputCustomizer;
|
||||||
private QCodeReference staticDataSupplier;
|
private QCodeReference staticDataSupplier;
|
||||||
|
private QCodeReference customRecordSource;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -265,4 +273,35 @@ public class QReportDataSource
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for customRecordSource
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getCustomRecordSource()
|
||||||
|
{
|
||||||
|
return (this.customRecordSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for customRecordSource
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setCustomRecordSource(QCodeReference customRecordSource)
|
||||||
|
{
|
||||||
|
this.customRecordSource = customRecordSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for customRecordSource
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource withCustomRecordSource(QCodeReference customRecordSource)
|
||||||
|
{
|
||||||
|
this.customRecordSource = customRecordSource;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,8 @@ public class BasicRunReportProcess
|
|||||||
public static final String STEP_NAME_EXECUTE = "execute";
|
public static final String STEP_NAME_EXECUTE = "execute";
|
||||||
public static final String STEP_NAME_ACCESS = "accessReport";
|
public static final String STEP_NAME_ACCESS = "accessReport";
|
||||||
|
|
||||||
public static final String FIELD_REPORT_NAME = "reportName";
|
public static final String FIELD_REPORT_NAME = "reportName";
|
||||||
|
public static final String FIELD_REPORT_FORMAT = "reportFormat";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
|
||||||
@ -50,6 +51,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|||||||
public class ExecuteReportStep implements BackendStep
|
public class ExecuteReportStep implements BackendStep
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -58,9 +60,10 @@ public class ExecuteReportStep implements BackendStep
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
String reportName = runBackendStepInput.getValueString("reportName");
|
ReportFormat reportFormat = getReportFormat(runBackendStepInput);
|
||||||
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
String reportName = runBackendStepInput.getValueString("reportName");
|
||||||
File tmpFile = File.createTempFile(reportName, ".xlsx", new File("/tmp/"));
|
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
||||||
|
File tmpFile = File.createTempFile(reportName, "." + reportFormat.getExtension());
|
||||||
|
|
||||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
|
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
|
||||||
|
|
||||||
@ -69,7 +72,7 @@ public class ExecuteReportStep implements BackendStep
|
|||||||
ReportInput reportInput = new ReportInput();
|
ReportInput reportInput = new ReportInput();
|
||||||
reportInput.setReportName(reportName);
|
reportInput.setReportName(reportName);
|
||||||
reportInput.setReportDestination(new ReportDestination()
|
reportInput.setReportDestination(new ReportDestination()
|
||||||
.withReportFormat(ReportFormat.XLSX) // todo - variable
|
.withReportFormat(reportFormat)
|
||||||
.withReportOutputStream(reportOutputStream));
|
.withReportOutputStream(reportOutputStream));
|
||||||
|
|
||||||
Map<String, Serializable> values = runBackendStepInput.getValues();
|
Map<String, Serializable> values = runBackendStepInput.getValues();
|
||||||
@ -79,7 +82,7 @@ public class ExecuteReportStep implements BackendStep
|
|||||||
|
|
||||||
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, report);
|
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, report);
|
||||||
|
|
||||||
runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + ".xlsx");
|
runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + "." + reportFormat.getExtension());
|
||||||
runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath());
|
runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,6 +94,22 @@ public class ExecuteReportStep implements BackendStep
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private ReportFormat getReportFormat(RunBackendStepInput runBackendStepInput) throws QUserFacingException
|
||||||
|
{
|
||||||
|
String reportFormatInput = runBackendStepInput.getValueString(BasicRunReportProcess.FIELD_REPORT_FORMAT);
|
||||||
|
if(StringUtils.hasContent(reportFormatInput))
|
||||||
|
{
|
||||||
|
return (ReportFormat.fromString(reportFormatInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ReportFormat.XLSX);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
@ -38,6 +40,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
@ -54,6 +57,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
|||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
@ -541,4 +545,89 @@ class QueryActionTest extends BaseTest
|
|||||||
assertEquals(1, QueryAction.execute(TestUtils.TABLE_NAME_SHAPE, new QQueryFilter(new QFilterCriteria("name", QCriteriaOperator.EQUALS, "SqUaRe"))).size());
|
assertEquals(1, QueryAction.execute(TestUtils.TABLE_NAME_SHAPE, new QQueryFilter(new QFilterCriteria("name", QCriteriaOperator.EQUALS, "SqUaRe"))).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testValidateFieldNamesToInclude() throws QException
|
||||||
|
{
|
||||||
|
/////////////////////////////
|
||||||
|
// cases that do not throw //
|
||||||
|
/////////////////////////////
|
||||||
|
QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(null));
|
||||||
|
|
||||||
|
QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(Set.of("id")));
|
||||||
|
|
||||||
|
QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(Set.of("id", "firstName", "lastName", "birthDate", "modifyDate", "createDate")));
|
||||||
|
|
||||||
|
QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_SHAPE).withSelect(true))
|
||||||
|
.withFieldNamesToInclude(Set.of("id", "firstName", "lastName", "birthDate", "modifyDate", "createDate")));
|
||||||
|
|
||||||
|
QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_SHAPE).withSelect(true))
|
||||||
|
.withFieldNamesToInclude(Set.of("id", "firstName", TestUtils.TABLE_NAME_SHAPE + ".id", TestUtils.TABLE_NAME_SHAPE + ".noOfSides")));
|
||||||
|
|
||||||
|
QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_SHAPE).withSelect(true).withAlias("s"))
|
||||||
|
.withFieldNamesToInclude(Set.of("id", "s.id", "s.noOfSides")));
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// cases that do throw! //
|
||||||
|
//////////////////////////
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(new HashSet<>())))
|
||||||
|
.hasMessageContaining("empty set of fieldNamesToInclude");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(Set.of("notAField"))))
|
||||||
|
.hasMessageContaining("1 unrecognized field name: notAField");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(new LinkedHashSet<>(List.of("notAField", "alsoNot")))))
|
||||||
|
.hasMessageContaining("2 unrecognized field names: notAField,alsoNot");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(new LinkedHashSet<>(List.of("notAField", "alsoNot", "join.not")))))
|
||||||
|
.hasMessageContaining("3 unrecognized field names: notAField,alsoNot,join.not");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_SHAPE)) // oops, didn't select it!
|
||||||
|
.withFieldNamesToInclude(new LinkedHashSet<>(List.of("id", "firstName", TestUtils.TABLE_NAME_SHAPE + ".id", TestUtils.TABLE_NAME_SHAPE + ".noOfSides")))))
|
||||||
|
.hasMessageContaining("2 unrecognized field names: shape.id,shape.noOfSides");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_SHAPE).withSelect(true))
|
||||||
|
.withFieldNamesToInclude(Set.of(TestUtils.TABLE_NAME_SHAPE + ".noField"))))
|
||||||
|
.hasMessageContaining("1 unrecognized field name: shape.noField");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_SHAPE).withSelect(true).withAlias("s")) // oops, not using alias
|
||||||
|
.withFieldNamesToInclude(new LinkedHashSet<>(List.of("id", "firstName", TestUtils.TABLE_NAME_SHAPE + ".id", "s.noOfSides")))))
|
||||||
|
.hasMessageContaining("1 unrecognized field name: shape.id");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_SHAPE).withSelect(true))
|
||||||
|
.withFieldNamesToInclude(new LinkedHashSet<>(List.of("id", "firstName", TestUtils.TABLE_NAME_SHAPE + ".id", "noOfSides")))))
|
||||||
|
.hasMessageContaining("1 unrecognized field name: noOfSides");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withQueryJoin(new QueryJoin("noJoinTable").withSelect(true))
|
||||||
|
.withFieldNamesToInclude(Set.of("noJoinTable.id"))))
|
||||||
|
.hasMessageContaining("1 unrecognized field name: noJoinTable.id");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(Set.of("noJoin.id"))))
|
||||||
|
.hasMessageContaining("1 unrecognized field name: noJoin.id");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> QueryAction.validateFieldNamesToInclude(new QueryInput(TestUtils.TABLE_NAME_PERSON)
|
||||||
|
.withFieldNamesToInclude(Set.of("noDb.noJoin.id"))))
|
||||||
|
.hasMessageContaining("1 unrecognized field name: noDb.noJoin.id");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
package com.kingsrook.qqq.backend.core.instances;
|
package com.kingsrook.qqq.backend.core.instances;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -38,6 +39,8 @@ import com.kingsrook.qqq.backend.core.actions.dashboard.PersonsByCreateDateBarCh
|
|||||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ParentWidgetRenderer;
|
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ParentWidgetRenderer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.processes.CancelProcessActionTest;
|
import com.kingsrook.qqq.backend.core.actions.processes.CancelProcessActionTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||||
@ -45,6 +48,7 @@ import com.kingsrook.qqq.backend.core.instances.validation.plugins.AlwaysFailsPr
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
@ -1712,7 +1716,7 @@ public class QInstanceValidatorTest extends BaseTest
|
|||||||
@Test
|
@Test
|
||||||
void testReportDataSourceStaticDataSupplier()
|
void testReportDataSourceStaticDataSupplier()
|
||||||
{
|
{
|
||||||
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).withStaticDataSupplier(new QCodeReference()),
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).withStaticDataSupplier(new QCodeReference(TestReportStaticDataSupplier.class)),
|
||||||
"has both a sourceTable and a staticDataSupplier");
|
"has both a sourceTable and a staticDataSupplier");
|
||||||
|
|
||||||
assertValidationFailureReasons((qInstance) ->
|
assertValidationFailureReasons((qInstance) ->
|
||||||
@ -1720,16 +1724,43 @@ public class QInstanceValidatorTest extends BaseTest
|
|||||||
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
|
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
|
||||||
dataSource.setSourceTable(null);
|
dataSource.setSourceTable(null);
|
||||||
dataSource.setStaticDataSupplier(new QCodeReference(null, QCodeType.JAVA));
|
dataSource.setStaticDataSupplier(new QCodeReference(null, QCodeType.JAVA));
|
||||||
},
|
}, "missing a code reference name");
|
||||||
"missing a code reference name");
|
|
||||||
|
|
||||||
assertValidationFailureReasons((qInstance) ->
|
assertValidationFailureReasons((qInstance) ->
|
||||||
{
|
{
|
||||||
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
|
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
|
||||||
dataSource.setSourceTable(null);
|
dataSource.setSourceTable(null);
|
||||||
dataSource.setStaticDataSupplier(new QCodeReference(ArrayList.class));
|
dataSource.setStaticDataSupplier(new QCodeReference(ArrayList.class));
|
||||||
},
|
}, "is not of the expected type");
|
||||||
"is not of the expected type");
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testReportDataSourceCustomRecordSource()
|
||||||
|
{
|
||||||
|
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0)
|
||||||
|
.withSourceTable(null)
|
||||||
|
.withStaticDataSupplier(new QCodeReference(TestReportStaticDataSupplier.class))
|
||||||
|
.withCustomRecordSource(new QCodeReference(TestReportCustomRecordSource.class)),
|
||||||
|
"has both a staticDataSupplier and a customRecordSource");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) ->
|
||||||
|
{
|
||||||
|
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
|
||||||
|
dataSource.setSourceTable(null);
|
||||||
|
dataSource.setCustomRecordSource(new QCodeReference(null, QCodeType.JAVA));
|
||||||
|
}, "missing a code reference name");
|
||||||
|
|
||||||
|
assertValidationFailureReasons((qInstance) ->
|
||||||
|
{
|
||||||
|
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
|
||||||
|
dataSource.setSourceTable(null);
|
||||||
|
dataSource.setCustomRecordSource(new QCodeReference(ArrayList.class));
|
||||||
|
}, "is not of the expected type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2371,5 +2402,38 @@ public class QInstanceValidatorTest extends BaseTest
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static class ValidAuthCustomizer implements QAuthenticationModuleCustomizerInterface {}
|
public static class ValidAuthCustomizer implements QAuthenticationModuleCustomizerInterface {}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public static class TestReportStaticDataSupplier implements Supplier<List<List<Serializable>>>
|
||||||
|
{
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<List<Serializable>> get()
|
||||||
|
{
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public static class TestReportCustomRecordSource implements ReportCustomRecordSourceInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void execute(ReportInput reportInput, QReportDataSource reportDataSource, RecordPipe recordPipe) throws QException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportActionTest
|
|||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
@ -43,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
class BasicRunReportProcessTest extends BaseTest
|
class BasicRunReportProcessTest extends BaseTest
|
||||||
{
|
{
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -74,6 +76,18 @@ class BasicRunReportProcessTest extends BaseTest
|
|||||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||||
assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo(BasicRunReportProcess.STEP_NAME_ACCESS);
|
assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo(BasicRunReportProcess.STEP_NAME_ACCESS);
|
||||||
assertThat(runProcessOutput.getValues()).containsKeys("downloadFileName", "serverFilePath");
|
assertThat(runProcessOutput.getValues()).containsKeys("downloadFileName", "serverFilePath");
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// assert we get xlsx by default //
|
||||||
|
///////////////////////////////////
|
||||||
|
assertThat(runProcessOutput.getValueString("downloadFileName")).endsWith(".xlsx");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// re-run, requesting CSV, then assert we get that //
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
runProcessInput.addValue(BasicRunReportProcess.FIELD_REPORT_FORMAT, ReportFormat.CSV.name());
|
||||||
|
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||||
|
assertThat(runProcessOutput.getValueString("downloadFileName")).endsWith(".csv");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
@ -176,18 +177,28 @@ public class AbstractMongoDBAction
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Convert a mongodb document to a QRecord.
|
** Convert a mongodb document to a QRecord.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
protected QRecord documentToRecord(QTableMetaData table, Document document)
|
protected QRecord documentToRecord(QueryInput queryInput, Document document)
|
||||||
{
|
{
|
||||||
QRecord record = new QRecord();
|
QTableMetaData table = queryInput.getTable();
|
||||||
|
QRecord record = new QRecord();
|
||||||
|
|
||||||
record.setTableName(table.getName());
|
record.setTableName(table.getName());
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// build the set of field names to include //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
Set<String> fieldNamesToInclude = queryInput.getFieldNamesToInclude();
|
||||||
|
List<QFieldMetaData> selectedFields = table.getFields().values()
|
||||||
|
.stream().filter(field -> fieldNamesToInclude == null || fieldNamesToInclude.contains(field.getName()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// first iterate over the table's fields, looking for them (at their backend name (path, //
|
// first iterate over the table's fields, looking for them (at their backend name (path, //
|
||||||
// if it has dots) inside the document note that we'll remove values from the document //
|
// if it has dots) inside the document note that we'll remove values from the document //
|
||||||
// as we go - then after this loop, will handle all remaining values as unstructured fields //
|
// as we go - then after this loop, will handle all remaining values as unstructured fields //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
Map<String, Serializable> values = record.getValues();
|
Map<String, Serializable> values = record.getValues();
|
||||||
for(QFieldMetaData field : table.getFields().values())
|
for(QFieldMetaData field : selectedFields)
|
||||||
{
|
{
|
||||||
String fieldName = field.getName();
|
String fieldName = field.getName();
|
||||||
String fieldBackendName = getFieldBackendName(field);
|
String fieldBackendName = getFieldBackendName(field);
|
||||||
|
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMet
|
|||||||
import com.mongodb.client.FindIterable;
|
import com.mongodb.client.FindIterable;
|
||||||
import com.mongodb.client.MongoCollection;
|
import com.mongodb.client.MongoCollection;
|
||||||
import com.mongodb.client.MongoDatabase;
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
import com.mongodb.client.model.Projections;
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
import org.bson.conversions.Bson;
|
import org.bson.conversions.Bson;
|
||||||
|
|
||||||
@ -96,6 +97,15 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
FindIterable<Document> cursor = collection.find(mongoClientContainer.getMongoSession(), searchQuery);
|
FindIterable<Document> cursor = collection.find(mongoClientContainer.getMongoSession(), searchQuery);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if input specifies a set of field names to include, then add a 'projection' to the cursor //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(queryInput.getFieldNamesToInclude() != null)
|
||||||
|
{
|
||||||
|
List<String> backendFieldNames = queryInput.getFieldNamesToInclude().stream().map(f -> getFieldBackendName(table.getField(f))).toList();
|
||||||
|
cursor.projection(Projections.include(backendFieldNames));
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
// add a sort operator if needed //
|
// add a sort operator if needed //
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
@ -138,7 +148,7 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
actionTimeoutHelper.cancel();
|
actionTimeoutHelper.cancel();
|
||||||
setQueryStatFirstResultTime();
|
setQueryStatFirstResultTime();
|
||||||
|
|
||||||
QRecord record = documentToRecord(table, document);
|
QRecord record = documentToRecord(queryInput, document);
|
||||||
queryOutput.addRecord(record);
|
queryOutput.addRecord(record);
|
||||||
|
|
||||||
if(queryInput.getAsyncJobCallback().wasCancelRequested())
|
if(queryInput.getAsyncJobCallback().wasCancelRequested())
|
||||||
|
@ -29,6 +29,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@ -54,6 +55,8 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -994,4 +997,46 @@ class MongoDBQueryActionTest extends BaseTest
|
|||||||
.allMatch(r -> r.getValueInteger("storeKey").equals(1));
|
.allMatch(r -> r.getValueInteger("storeKey").equals(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testFieldNamesToInclude() throws QException
|
||||||
|
{
|
||||||
|
QQueryFilter filter = new QQueryFilter().withCriteria("firstName", QCriteriaOperator.EQUALS, "Darin");
|
||||||
|
QueryInput queryInput = new QueryInput(TestUtils.TABLE_NAME_PERSON).withFilter(filter);
|
||||||
|
|
||||||
|
QRecord record = new QueryAction().execute(queryInput.withFieldNamesToInclude(null)).getRecords().get(0);
|
||||||
|
assertTrue(record.getValues().containsKey("id"));
|
||||||
|
assertTrue(record.getValues().containsKey("firstName"));
|
||||||
|
assertTrue(record.getValues().containsKey("createDate"));
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// note, we get an extra "metaData" field (??), which, i guess is expected? //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertEquals(QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON).getFields().size() + 1, record.getValues().size());
|
||||||
|
|
||||||
|
record = new QueryAction().execute(queryInput.withFieldNamesToInclude(Set.of("id", "firstName"))).getRecords().get(0);
|
||||||
|
assertTrue(record.getValues().containsKey("id"));
|
||||||
|
assertTrue(record.getValues().containsKey("firstName"));
|
||||||
|
assertFalse(record.getValues().containsKey("createDate"));
|
||||||
|
assertEquals(2, record.getValues().size());
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// here, we'd have put _id (which mongo always returns) as "id", since caller requested it. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertFalse(record.getValues().containsKey("_id"));
|
||||||
|
|
||||||
|
record = new QueryAction().execute(queryInput.withFieldNamesToInclude(Set.of("homeTown"))).getRecords().get(0);
|
||||||
|
assertFalse(record.getValues().containsKey("id"));
|
||||||
|
assertFalse(record.getValues().containsKey("firstName"));
|
||||||
|
assertFalse(record.getValues().containsKey("createDate"));
|
||||||
|
assertTrue(record.getValues().containsKey("homeTown"));
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// here, mongo always gives back _id (but, we won't have re-mapped it to "id", since caller didn't request it), so, do expect 2 fields here //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertEquals(2, record.getValues().size());
|
||||||
|
assertTrue(record.getValues().containsKey("_id"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -34,6 +34,7 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||||
@ -94,7 +95,8 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
QTableMetaData table = queryInput.getTable();
|
QTableMetaData table = queryInput.getTable();
|
||||||
String tableName = queryInput.getTableName();
|
String tableName = queryInput.getTableName();
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder(makeSelectClause(queryInput));
|
Selection selection = makeSelection(queryInput);
|
||||||
|
StringBuilder sql = new StringBuilder(selection.selectClause());
|
||||||
|
|
||||||
QQueryFilter filter = clonedOrNewFilter(queryInput.getFilter());
|
QQueryFilter filter = clonedOrNewFilter(queryInput.getFilter());
|
||||||
JoinsContext joinsContext = new JoinsContext(QContext.getQInstance(), tableName, queryInput.getQueryJoins(), filter);
|
JoinsContext joinsContext = new JoinsContext(QContext.getQInstance(), tableName, queryInput.getQueryJoins(), filter);
|
||||||
@ -135,23 +137,6 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
needToCloseConnection = true;
|
needToCloseConnection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
// build the list of fields that will be processed in the result-set loop //
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values().stream().toList());
|
|
||||||
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryInput.getQueryJoins()))
|
|
||||||
{
|
|
||||||
if(queryJoin.getSelect())
|
|
||||||
{
|
|
||||||
QTableMetaData joinTable = QContext.getQInstance().getTable(queryJoin.getJoinTable());
|
|
||||||
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
|
||||||
for(QFieldMetaData joinField : joinTable.getFields().values())
|
|
||||||
{
|
|
||||||
fieldList.add(joinField.clone().withName(tableNameOrAlias + "." + joinField.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Long mark = System.currentTimeMillis();
|
Long mark = System.currentTimeMillis();
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -199,7 +184,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
|
|
||||||
for(int i = 1; i <= metaData.getColumnCount(); i++)
|
for(int i = 1; i <= metaData.getColumnCount(); i++)
|
||||||
{
|
{
|
||||||
QFieldMetaData field = fieldList.get(i - 1);
|
QFieldMetaData field = selection.fields().get(i - 1);
|
||||||
|
|
||||||
if(!queryInput.getShouldFetchHeavyFields() && field.getIsHeavy())
|
if(!queryInput.getShouldFetchHeavyFields() && field.getIsHeavy())
|
||||||
{
|
{
|
||||||
@ -294,26 +279,62 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** output wrapper for makeSelection method.
|
||||||
|
** - selectClause is everything from SELECT up to (but not including) FROM
|
||||||
|
** - fields are those being selected, in the same order, and with mutated
|
||||||
|
** names for join fields.
|
||||||
|
***************************************************************************/
|
||||||
|
private record Selection(String selectClause, List<QFieldMetaData> fields)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
** For a given queryInput, determine what fields are being selected - returning
|
||||||
|
** a record containing the SELECT clause, as well as a List of QFieldMetaData
|
||||||
|
** representing those fields - where - note - the names for fields from join
|
||||||
|
** tables will be prefixed by the join table nameOrAlias.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private String makeSelectClause(QueryInput queryInput) throws QException
|
private Selection makeSelection(QueryInput queryInput) throws QException
|
||||||
{
|
{
|
||||||
QInstance instance = QContext.getQInstance();
|
QInstance instance = QContext.getQInstance();
|
||||||
String tableName = queryInput.getTableName();
|
String tableName = queryInput.getTableName();
|
||||||
List<QueryJoin> queryJoins = queryInput.getQueryJoins();
|
List<QueryJoin> queryJoins = queryInput.getQueryJoins();
|
||||||
QTableMetaData table = instance.getTable(tableName);
|
QTableMetaData table = instance.getTable(tableName);
|
||||||
|
|
||||||
boolean requiresDistinct = queryInput.getSelectDistinct() || doesSelectClauseRequireDistinct(table);
|
Set<String> fieldNamesToInclude = queryInput.getFieldNamesToInclude();
|
||||||
String clausePrefix = (requiresDistinct) ? "SELECT DISTINCT " : "SELECT ";
|
|
||||||
|
|
||||||
List<QFieldMetaData> fieldList = new ArrayList<>(table.getFields().values());
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// start with the main table's fields, optionally filtered by the set of fieldNamesToInclude //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
List<QFieldMetaData> fieldList = table.getFields().values()
|
||||||
|
.stream().filter(field -> fieldNamesToInclude == null || fieldNamesToInclude.contains(field.getName()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// map those field names to columns, joined with ", ". //
|
||||||
|
// if a field is heavy, and heavy fields aren't being selected, then replace that field name with a LENGTH function //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
String columns = fieldList.stream()
|
String columns = fieldList.stream()
|
||||||
.map(field -> Pair.of(field, escapeIdentifier(tableName) + "." + escapeIdentifier(getColumnName(field))))
|
.map(field -> Pair.of(field, escapeIdentifier(tableName) + "." + escapeIdentifier(getColumnName(field))))
|
||||||
.map(pair -> wrapHeavyFieldsWithLengthFunctionIfNeeded(pair, queryInput.getShouldFetchHeavyFields()))
|
.map(pair -> wrapHeavyFieldsWithLengthFunctionIfNeeded(pair, queryInput.getShouldFetchHeavyFields()))
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
StringBuilder rs = new StringBuilder(clausePrefix).append(columns);
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// figure out if distinct is being used. then start building the select clause with the table's columns //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
boolean requiresDistinct = queryInput.getSelectDistinct() || doesSelectClauseRequireDistinct(table);
|
||||||
|
StringBuilder selectClause = new StringBuilder((requiresDistinct) ? "SELECT DISTINCT " : "SELECT ").append(columns);
|
||||||
|
List<QFieldMetaData> selectionFieldList = new ArrayList<>(fieldList);
|
||||||
|
|
||||||
|
boolean needCommaBeforeJoinFields = !columns.isEmpty();
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// add any 'selected' queryJoins //
|
||||||
|
///////////////////////////////////
|
||||||
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
|
||||||
{
|
{
|
||||||
if(queryJoin.getSelect())
|
if(queryJoin.getSelect())
|
||||||
@ -325,16 +346,41 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
throw new QException("Requested join table [" + queryJoin.getJoinTable() + "] is not a defined table.");
|
throw new QException("Requested join table [" + queryJoin.getJoinTable() + "] is not a defined table.");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<QFieldMetaData> joinFieldList = new ArrayList<>(joinTable.getFields().values());
|
///////////////////////////////////
|
||||||
|
// filter by fieldNamesToInclude //
|
||||||
|
///////////////////////////////////
|
||||||
|
List<QFieldMetaData> joinFieldList = joinTable.getFields().values()
|
||||||
|
.stream().filter(field -> fieldNamesToInclude == null || fieldNamesToInclude.contains(tableNameOrAlias + "." + field.getName()))
|
||||||
|
.toList();
|
||||||
|
if(joinFieldList.isEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// map to columns, wrapping heavy fields as needed //
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
String joinColumns = joinFieldList.stream()
|
String joinColumns = joinFieldList.stream()
|
||||||
.map(field -> Pair.of(field, escapeIdentifier(tableNameOrAlias) + "." + escapeIdentifier(getColumnName(field))))
|
.map(field -> Pair.of(field, escapeIdentifier(tableNameOrAlias) + "." + escapeIdentifier(getColumnName(field))))
|
||||||
.map(pair -> wrapHeavyFieldsWithLengthFunctionIfNeeded(pair, queryInput.getShouldFetchHeavyFields()))
|
.map(pair -> wrapHeavyFieldsWithLengthFunctionIfNeeded(pair, queryInput.getShouldFetchHeavyFields()))
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
rs.append(", ").append(joinColumns);
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// append to output objects. //
|
||||||
|
// note that fields are cloned, since we are changing their names to have table/alias prefix. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(needCommaBeforeJoinFields)
|
||||||
|
{
|
||||||
|
selectClause.append(", ");
|
||||||
|
}
|
||||||
|
selectClause.append(joinColumns);
|
||||||
|
needCommaBeforeJoinFields = true;
|
||||||
|
|
||||||
|
selectionFieldList.addAll(joinFieldList.stream().map(field -> field.clone().withName(tableNameOrAlias + "." + field.getName())).toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rs.toString());
|
return (new Selection(selectClause.toString(), selectionFieldList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
@ -1052,4 +1053,48 @@ public class RDBMSQueryActionJoinsTest extends RDBMSActionTest
|
|||||||
assertEquals(5, new QueryAction().execute(new QueryInput(TestUtils.TABLE_NAME_PERSON)).getRecords().size());
|
assertEquals(5, new QueryAction().execute(new QueryInput(TestUtils.TABLE_NAME_PERSON)).getRecords().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testFieldNamesToInclude() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true));
|
||||||
|
queryInput.withFieldNamesToInclude(Set.of("firstName", TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber"));
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().containsKey("firstName"));
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().containsKey(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber"));
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().size() == 2);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// re-run w/ null fieldNamesToInclude -- and should still see those 2, and more (values size > 2) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withFieldNamesToInclude(null);
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().containsKey("firstName"));
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().containsKey(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber"));
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().size() > 2);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// regression from original build - where only join fields made sql error //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withFieldNamesToInclude(Set.of(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber"));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().containsKey(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber"));
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().size() == 1);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// similar regression to above, if one of the join tables didn't have anything selected //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withAlias("id2").withSelect(true));
|
||||||
|
queryInput.withFieldNamesToInclude(Set.of("firstName", "id2.idNumber"));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().containsKey("firstName"));
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().containsKey("id2.idNumber"));
|
||||||
|
assertThat(queryOutput.getRecords()).allMatch(r -> r.getValues().size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
@ -48,11 +49,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock
|
|||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -166,7 +168,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").equals(email)), "Should NOT find expected email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -199,7 +201,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withValues(List.of(1_000_000))));
|
.withValues(List.of(1_000_000))));
|
||||||
queryOutput = new RDBMSQueryAction().execute(queryInput);
|
queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> Objects.equals(1_000_000, r.getValueInteger("annualSalary"))), "Should NOT find expected salary");
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> Objects.equals(1_000_000, r.getValueInteger("annualSalary"))), "Should NOT find expected salary");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -219,7 +221,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(2) || r.getValueInteger("id").equals(4)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -239,7 +241,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -259,7 +261,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -279,7 +281,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -299,7 +301,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -319,7 +321,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -339,7 +341,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -359,7 +361,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -379,7 +381,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -399,7 +401,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -419,7 +421,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -439,7 +441,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -459,7 +461,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -479,7 +481,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
);
|
);
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -498,7 +500,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("birthDate") == null), "Should find expected row");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -517,7 +519,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("firstName") != null), "Should find expected rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -537,7 +539,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -557,7 +559,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
));
|
));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
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");
|
assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(5)), "Should find expected ids");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -583,7 +585,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.LESS_THAN).withValues(List.of(new Now()))));
|
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.LESS_THAN).withValues(List.of(new Now()))));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -593,7 +595,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.LESS_THAN).withValues(List.of(NowWithOffset.plus(2, ChronoUnit.DAYS)))));
|
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.LESS_THAN).withValues(List.of(NowWithOffset.plus(2, ChronoUnit.DAYS)))));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -603,8 +605,8 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.GREATER_THAN).withValues(List.of(NowWithOffset.minus(5, ChronoUnit.DAYS)))));
|
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.GREATER_THAN).withValues(List.of(NowWithOffset.minus(5, ChronoUnit.DAYS)))));
|
||||||
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
QueryOutput queryOutput = new RDBMSQueryAction().execute(queryInput);
|
||||||
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
||||||
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("future")), "Should find expected row");
|
assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("future")), "Should find expected row");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1005,7 +1007,36 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
|||||||
queryInput.setShouldFetchHeavyFields(true);
|
queryInput.setShouldFetchHeavyFields(true);
|
||||||
records = new QueryAction().execute(queryInput).getRecords();
|
records = new QueryAction().execute(queryInput).getRecords();
|
||||||
assertThat(records).describedAs("Some records should have the heavy homeTown field set when heavies are requested").anyMatch(r -> r.getValue("homeTown") != null);
|
assertThat(records).describedAs("Some records should have the heavy homeTown field set when heavies are requested").anyMatch(r -> r.getValue("homeTown") != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testFieldNamesToInclude() throws QException
|
||||||
|
{
|
||||||
|
QQueryFilter filter = new QQueryFilter().withCriteria("id", QCriteriaOperator.EQUALS, 1);
|
||||||
|
QueryInput queryInput = new QueryInput(TestUtils.TABLE_NAME_PERSON).withFilter(filter);
|
||||||
|
|
||||||
|
QRecord record = new QueryAction().execute(queryInput.withFieldNamesToInclude(null)).getRecords().get(0);
|
||||||
|
assertTrue(record.getValues().containsKey("id"));
|
||||||
|
assertTrue(record.getValues().containsKey("firstName"));
|
||||||
|
assertTrue(record.getValues().containsKey("createDate"));
|
||||||
|
assertEquals(QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON).getFields().size(), record.getValues().size());
|
||||||
|
|
||||||
|
record = new QueryAction().execute(queryInput.withFieldNamesToInclude(Set.of("id", "firstName"))).getRecords().get(0);
|
||||||
|
assertTrue(record.getValues().containsKey("id"));
|
||||||
|
assertTrue(record.getValues().containsKey("firstName"));
|
||||||
|
assertFalse(record.getValues().containsKey("createDate"));
|
||||||
|
assertEquals(2, record.getValues().size());
|
||||||
|
|
||||||
|
record = new QueryAction().execute(queryInput.withFieldNamesToInclude(Set.of("homeTown"))).getRecords().get(0);
|
||||||
|
assertFalse(record.getValues().containsKey("id"));
|
||||||
|
assertFalse(record.getValues().containsKey("firstName"));
|
||||||
|
assertFalse(record.getValues().containsKey("createDate"));
|
||||||
|
assertEquals(1, record.getValues().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user