mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-25 16:58:45 +00:00
Compare commits
2 Commits
snapshot
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
99a786e4ab | |||
65ab6bfa87 |
@ -49,7 +49,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -79,12 +78,6 @@ public class PermissionsHelper
|
|||||||
warnAboutPermissionSubTypeForTables(permissionSubType);
|
warnAboutPermissionSubTypeForTables(permissionSubType);
|
||||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||||
|
|
||||||
if(table == null)
|
|
||||||
{
|
|
||||||
LOG.info("Throwing a permission denied exception in response to a non-existent table name", logPair("tableName", tableName));
|
|
||||||
throw (new QPermissionDeniedException("Permission denied."));
|
|
||||||
}
|
|
||||||
|
|
||||||
commonCheckPermissionThrowing(getEffectivePermissionRules(table, QContext.getQInstance()), permissionSubType, table.getName());
|
commonCheckPermissionThrowing(getEffectivePermissionRules(table, QContext.getQInstance()), permissionSubType, table.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,14 +184,7 @@ public class PermissionsHelper
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void checkProcessPermissionThrowing(AbstractActionInput actionInput, String processName, Map<String, Serializable> processValues) throws QPermissionDeniedException
|
public static void checkProcessPermissionThrowing(AbstractActionInput actionInput, String processName, Map<String, Serializable> processValues) throws QPermissionDeniedException
|
||||||
{
|
{
|
||||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||||
|
|
||||||
if(process == null)
|
|
||||||
{
|
|
||||||
LOG.info("Throwing a permission denied exception in response to a non-existent process name", logPair("processName", processName));
|
|
||||||
throw (new QPermissionDeniedException("Permission denied."));
|
|
||||||
}
|
|
||||||
|
|
||||||
QPermissionRules effectivePermissionRules = getEffectivePermissionRules(process, QContext.getQInstance());
|
QPermissionRules effectivePermissionRules = getEffectivePermissionRules(process, QContext.getQInstance());
|
||||||
|
|
||||||
if(effectivePermissionRules.getCustomPermissionChecker() != null)
|
if(effectivePermissionRules.getCustomPermissionChecker() != null)
|
||||||
@ -240,13 +226,6 @@ public class PermissionsHelper
|
|||||||
public static void checkAppPermissionThrowing(AbstractActionInput actionInput, String appName) throws QPermissionDeniedException
|
public static void checkAppPermissionThrowing(AbstractActionInput actionInput, String appName) throws QPermissionDeniedException
|
||||||
{
|
{
|
||||||
QAppMetaData app = QContext.getQInstance().getApp(appName);
|
QAppMetaData app = QContext.getQInstance().getApp(appName);
|
||||||
|
|
||||||
if(app == null)
|
|
||||||
{
|
|
||||||
LOG.info("Throwing a permission denied exception in response to a non-existent app name", logPair("appName", appName));
|
|
||||||
throw (new QPermissionDeniedException("Permission denied."));
|
|
||||||
}
|
|
||||||
|
|
||||||
commonCheckPermissionThrowing(getEffectivePermissionRules(app, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName());
|
commonCheckPermissionThrowing(getEffectivePermissionRules(app, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,13 +255,6 @@ public class PermissionsHelper
|
|||||||
public static void checkReportPermissionThrowing(AbstractActionInput actionInput, String reportName) throws QPermissionDeniedException
|
public static void checkReportPermissionThrowing(AbstractActionInput actionInput, String reportName) throws QPermissionDeniedException
|
||||||
{
|
{
|
||||||
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
||||||
|
|
||||||
if(report == null)
|
|
||||||
{
|
|
||||||
LOG.info("Throwing a permission denied exception in response to a non-existent process name", logPair("reportName", reportName));
|
|
||||||
throw (new QPermissionDeniedException("Permission denied."));
|
|
||||||
}
|
|
||||||
|
|
||||||
commonCheckPermissionThrowing(getEffectivePermissionRules(report, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName());
|
commonCheckPermissionThrowing(getEffectivePermissionRules(report, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,13 +284,6 @@ public class PermissionsHelper
|
|||||||
public static void checkWidgetPermissionThrowing(AbstractActionInput actionInput, String widgetName) throws QPermissionDeniedException
|
public static void checkWidgetPermissionThrowing(AbstractActionInput actionInput, String widgetName) throws QPermissionDeniedException
|
||||||
{
|
{
|
||||||
QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName);
|
QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName);
|
||||||
|
|
||||||
if(widget == null)
|
|
||||||
{
|
|
||||||
LOG.info("Throwing a permission denied exception in response to a non-existent widget name", logPair("widgetName", widgetName));
|
|
||||||
throw (new QPermissionDeniedException("Permission denied."));
|
|
||||||
}
|
|
||||||
|
|
||||||
commonCheckPermissionThrowing(getEffectivePermissionRules(widget, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName());
|
commonCheckPermissionThrowing(getEffectivePermissionRules(widget, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Utility for verifying that the ExportAction works for all tables, and all
|
|
||||||
** exposed joins.
|
|
||||||
**
|
|
||||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
|
||||||
** validation.
|
|
||||||
*******************************************************************************/
|
|
||||||
public class ExportsFullInstanceVerifier
|
|
||||||
{
|
|
||||||
private static final QLogger LOG = QLogger.getLogger(ExportsFullInstanceVerifier.class);
|
|
||||||
|
|
||||||
private boolean filterForAtMostOneRowPerExport = true;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void verify(Collection<QTableMetaData> tables) throws QException
|
|
||||||
{
|
|
||||||
Map<Pair<String, String>, Exception> caughtExceptions = new LinkedHashMap<>();
|
|
||||||
for(QTableMetaData table : tables)
|
|
||||||
{
|
|
||||||
if(table.isCapabilityEnabled(QContext.getQInstance().getBackendForTable(table.getName()), Capability.TABLE_QUERY))
|
|
||||||
{
|
|
||||||
LOG.info("Verifying Exports on table", logPair("tableName", table.getName()));
|
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
// run the table by itself (no join fields) //
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
runExport(table.getName(), Collections.emptyList(), "main-table-only", caughtExceptions);
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
// run once w/ the fields from each exposed join //
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
|
||||||
{
|
|
||||||
runExport(table.getName(), List.of(exposedJoin), "join-" + exposedJoin.getLabel(), caughtExceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
// run w/ all exposed joins (if there are any) //
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
if(CollectionUtils.nullSafeHasContents(table.getExposedJoins()))
|
|
||||||
{
|
|
||||||
runExport(table.getName(), table.getExposedJoins(), "all-joins", caughtExceptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// log out an exceptions caught //
|
|
||||||
//////////////////////////////////
|
|
||||||
if(!caughtExceptions.isEmpty())
|
|
||||||
{
|
|
||||||
for(Map.Entry<Pair<String, String>, Exception> entry : caughtExceptions.entrySet())
|
|
||||||
{
|
|
||||||
LOG.info("Caught an exception verifying reports", entry.getValue(), logPair("tableName", entry.getKey().getA()), logPair("fieldName", entry.getKey().getB()));
|
|
||||||
}
|
|
||||||
throw (new QException("Reports Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private void runExport(String tableName, List<ExposedJoin> exposedJoinList, String description, Map<Pair<String, String>, Exception> caughtExceptions)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// build the list of fieldNames to export - starting with all fields in the table //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
List<String> fieldNames = new ArrayList<>();
|
|
||||||
for(QFieldMetaData field : QContext.getQInstance().getTable(tableName).getFields().values())
|
|
||||||
{
|
|
||||||
fieldNames.add(field.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
// add all fields from all exposed joins as well //
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(exposedJoinList))
|
|
||||||
{
|
|
||||||
QTableMetaData joinTable = QContext.getQInstance().getTable(exposedJoin.getJoinTable());
|
|
||||||
for(QFieldMetaData field : joinTable.getFields().values())
|
|
||||||
{
|
|
||||||
fieldNames.add(joinTable.getName() + "." + field.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.info("Verifying export", logPair("description", description), logPair("fieldCount", fieldNames.size()));
|
|
||||||
|
|
||||||
QQueryFilter queryFilter = new QQueryFilter();
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// if caller is okay with a filter that should limit the report to a small number of rows (could be more than 1 for to-many joins), then do so //
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
if(filterForAtMostOneRowPerExport)
|
|
||||||
{
|
|
||||||
queryFilter.withCriteria(QContext.getQInstance().getTable(tableName).getPrimaryKeyField(), QCriteriaOperator.EQUALS, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExportInput exportInput = new ExportInput();
|
|
||||||
exportInput.setTableName(tableName);
|
|
||||||
exportInput.setFieldNames(fieldNames);
|
|
||||||
exportInput.setReportDestination(new ReportDestination()
|
|
||||||
.withReportOutputStream(new ByteArrayOutputStream())
|
|
||||||
.withReportFormat(ReportFormat.CSV));
|
|
||||||
exportInput.setQueryFilter(queryFilter);
|
|
||||||
new ExportAction().execute(exportInput);
|
|
||||||
}
|
|
||||||
catch(QException e)
|
|
||||||
{
|
|
||||||
caughtExceptions.put(Pair.of(tableName, description), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for filterForAtMostOneRowPerExport
|
|
||||||
*******************************************************************************/
|
|
||||||
public boolean getFilterForAtMostOneRowPerExport()
|
|
||||||
{
|
|
||||||
return (this.filterForAtMostOneRowPerExport);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for filterForAtMostOneRowPerExport
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setFilterForAtMostOneRowPerExport(boolean filterForAtMostOneRowPerExport)
|
|
||||||
{
|
|
||||||
this.filterForAtMostOneRowPerExport = filterForAtMostOneRowPerExport;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for filterForAtMostOneRowPerExport
|
|
||||||
*******************************************************************************/
|
|
||||||
public ExportsFullInstanceVerifier withFilterForAtMostOneRowPerExport(boolean filterForAtMostOneRowPerExport)
|
|
||||||
{
|
|
||||||
this.filterForAtMostOneRowPerExport = filterForAtMostOneRowPerExport;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -66,7 +66,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
|||||||
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.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.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
@ -304,7 +303,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
{
|
{
|
||||||
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());
|
joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), dataSource.getQueryFilter());
|
||||||
countDataSourceRecords(reportInput, dataSource, reportFormat);
|
countDataSourceRecords(reportInput, dataSource, reportFormat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -352,7 +351,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
CountInput countInput = new CountInput();
|
CountInput countInput = new CountInput();
|
||||||
countInput.setTableName(dataSource.getSourceTable());
|
countInput.setTableName(dataSource.getSourceTable());
|
||||||
countInput.setFilter(queryFilter);
|
countInput.setFilter(queryFilter);
|
||||||
countInput.setQueryJoins(cloneDataSourceQueryJoins(dataSource));
|
countInput.setQueryJoins(dataSource.getQueryJoins());
|
||||||
CountOutput countOutput = new CountAction().execute(countInput);
|
CountOutput countOutput = new CountAction().execute(countInput);
|
||||||
|
|
||||||
if(countOutput.getCount() != null)
|
if(countOutput.getCount() != null)
|
||||||
@ -370,26 +369,6 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private static List<QueryJoin> cloneDataSourceQueryJoins(QReportDataSource dataSource)
|
|
||||||
{
|
|
||||||
if(dataSource == null || dataSource.getQueryJoins() == null)
|
|
||||||
{
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<QueryJoin> rs = new ArrayList<>();
|
|
||||||
for(QueryJoin queryJoin : dataSource.getQueryJoins())
|
|
||||||
{
|
|
||||||
rs.add(queryJoin.clone());
|
|
||||||
}
|
|
||||||
return (rs);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -438,12 +417,12 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
queryInput.setRecordPipe(recordPipe);
|
queryInput.setRecordPipe(recordPipe);
|
||||||
queryInput.setTableName(dataSource.getSourceTable());
|
queryInput.setTableName(dataSource.getSourceTable());
|
||||||
queryInput.setFilter(queryFilter);
|
queryInput.setFilter(queryFilter);
|
||||||
queryInput.setQueryJoins(cloneDataSourceQueryJoins(dataSource));
|
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||||
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||||
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||||
|
|
||||||
queryInput.setShouldTranslatePossibleValues(true);
|
queryInput.setShouldTranslatePossibleValues(true);
|
||||||
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource));
|
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter())));
|
||||||
|
|
||||||
if(dataSource.getQueryInputCustomizer() != null)
|
if(dataSource.getQueryInputCustomizer() != null)
|
||||||
{
|
{
|
||||||
@ -495,7 +474,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
}
|
}
|
||||||
consumedCount.getAndAdd(records.size());
|
consumedCount.getAndAdd(records.size());
|
||||||
|
|
||||||
return (consumeRecords(dataSource, records, tableView, summaryViews, variantViews));
|
return (consumeRecords(reportInput, dataSource, records, tableView, summaryViews, variantViews));
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
@ -514,7 +493,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private Set<String> setupFieldsToTranslatePossibleValues(ReportInput reportInput, QReportDataSource dataSource) throws QException
|
private Set<String> setupFieldsToTranslatePossibleValues(ReportInput reportInput, QReportDataSource dataSource, JoinsContext joinsContext) throws QException
|
||||||
{
|
{
|
||||||
Set<String> fieldsToTranslatePossibleValues = new HashSet<>();
|
Set<String> fieldsToTranslatePossibleValues = new HashSet<>();
|
||||||
|
|
||||||
@ -595,9 +574,9 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private Integer consumeRecords(QReportDataSource dataSource, List<QRecord> records, QReportView tableView, List<QReportView> summaryViews, List<QReportView> variantViews) throws QException
|
private Integer consumeRecords(ReportInput reportInput, QReportDataSource dataSource, List<QRecord> records, QReportView tableView, List<QReportView> summaryViews, List<QReportView> variantViews) throws QException
|
||||||
{
|
{
|
||||||
QTableMetaData table = QContext.getQInstance().getTable(dataSource.getSourceTable());
|
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// if this record goes on a table view, add it to the report streamer now //
|
// if this record goes on a table view, add it to the report streamer now //
|
||||||
@ -708,7 +687,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void addRecordToSummaryKeyAggregates(QTableMetaData table, QRecord record, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>> viewAggregates, SummaryKey key)
|
private void addRecordToSummaryKeyAggregates(QTableMetaData table, QRecord record, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>> viewAggregates, SummaryKey key) throws QException
|
||||||
{
|
{
|
||||||
Map<String, AggregatesInterface<?, ?>> keyAggregates = viewAggregates.computeIfAbsent(key, (name) -> new HashMap<>());
|
Map<String, AggregatesInterface<?, ?>> keyAggregates = viewAggregates.computeIfAbsent(key, (name) -> new HashMap<>());
|
||||||
addRecordToAggregatesMap(table, record, keyAggregates);
|
addRecordToAggregatesMap(table, record, keyAggregates);
|
||||||
@ -719,7 +698,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void addRecordToAggregatesMap(QTableMetaData table, QRecord record, Map<String, AggregatesInterface<?, ?>> aggregatesMap)
|
private void addRecordToAggregatesMap(QTableMetaData table, QRecord record, Map<String, AggregatesInterface<?, ?>> aggregatesMap) throws QException
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
// todo - an optimization could be, to only compute aggregates that we'll need... //
|
// todo - an optimization could be, to only compute aggregates that we'll need... //
|
||||||
@ -727,7 +706,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
for(String fieldName : record.getValues().keySet())
|
for(String fieldName : record.getValues().keySet())
|
||||||
{
|
{
|
||||||
QFieldMetaData field;
|
QFieldMetaData field = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
@ -800,14 +779,9 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
List<QReportView> reportViews = views.stream().filter(v -> v.getType().equals(ReportType.SUMMARY)).toList();
|
List<QReportView> reportViews = views.stream().filter(v -> v.getType().equals(ReportType.SUMMARY)).toList();
|
||||||
for(QReportView view : reportViews)
|
for(QReportView view : reportViews)
|
||||||
{
|
{
|
||||||
QReportDataSource dataSource = getDataSource(view.getDataSourceName());
|
QReportDataSource dataSource = getDataSource(view.getDataSourceName());
|
||||||
if(dataSource == null)
|
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||||
{
|
SummaryOutput summaryOutput = computeSummaryRowsForView(reportInput, view, table);
|
||||||
throw new QReportingException("Data source for summary view was not found (viewName=" + view.getName() + ", dataSourceName=" + view.getDataSourceName() + ").");
|
|
||||||
}
|
|
||||||
|
|
||||||
QTableMetaData table = QContext.getQInstance().getTable(dataSource.getSourceTable());
|
|
||||||
SummaryOutput summaryOutput = computeSummaryRowsForView(reportInput, view, table);
|
|
||||||
|
|
||||||
ExportInput exportInput = new ExportInput();
|
ExportInput exportInput = new ExportInput();
|
||||||
exportInput.setReportDestination(reportInput.getReportDestination());
|
exportInput.setReportDestination(reportInput.getReportDestination());
|
||||||
@ -893,8 +867,9 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private SummaryOutput computeSummaryRowsForView(ReportInput reportInput, QReportView view, QTableMetaData table) throws QFormulaException
|
private SummaryOutput computeSummaryRowsForView(ReportInput reportInput, QReportView view, QTableMetaData table) throws QReportingException, QFormulaException
|
||||||
{
|
{
|
||||||
|
QValueFormatter valueFormatter = new QValueFormatter();
|
||||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||||
variableInterpreter.addValueMap("input", reportInput.getInputValues());
|
variableInterpreter.addValueMap("input", reportInput.getInputValues());
|
||||||
variableInterpreter.addValueMap("total", getSummaryValuesForInterpreter(totalAggregates));
|
variableInterpreter.addValueMap("total", getSummaryValuesForInterpreter(totalAggregates));
|
||||||
@ -966,7 +941,10 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(CollectionUtils.nullSafeHasContents(view.getOrderByFields()))
|
if(CollectionUtils.nullSafeHasContents(view.getOrderByFields()))
|
||||||
{
|
{
|
||||||
summaryRows.sort((o1, o2) -> summaryRowComparator(view, o1, o2));
|
summaryRows.sort((o1, o2) ->
|
||||||
|
{
|
||||||
|
return summaryRowComparator(view, o1, o2);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
@ -1001,6 +979,8 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
Serializable serializable = getValueForColumn(variableInterpreter, column);
|
Serializable serializable = getValueForColumn(variableInterpreter, column);
|
||||||
totalRow.setValue(column.getName(), serializable);
|
totalRow.setValue(column.getName(), serializable);
|
||||||
thisRowValues.put(column.getName(), serializable);
|
thisRowValues.put(column.getName(), serializable);
|
||||||
|
|
||||||
|
String formatted = valueFormatter.formatValue(column.getDisplayFormat(), serializable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1023,7 +1003,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
titleValues.add(variableInterpreter.interpret(titleField));
|
titleValues.add(variableInterpreter.interpret(titleField));
|
||||||
}
|
}
|
||||||
|
|
||||||
title = QValueFormatter.formatStringWithValues(view.getTitleFormat(), titleValues);
|
title = new QValueFormatter().formatStringWithValues(view.getTitleFormat(), titleValues);
|
||||||
}
|
}
|
||||||
else if(StringUtils.hasContent(view.getTitleFormat()))
|
else if(StringUtils.hasContent(view.getTitleFormat()))
|
||||||
{
|
{
|
||||||
|
@ -280,16 +280,6 @@ public class QLogger
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void debug(LogPair... logPairs)
|
|
||||||
{
|
|
||||||
logger.warn(() -> makeJsonString(null, null, logPairs));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -430,16 +420,6 @@ public class QLogger
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void warn(LogPair... logPairs)
|
|
||||||
{
|
|
||||||
logger.warn(() -> makeJsonString(null, null, logPairs));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -500,16 +480,6 @@ public class QLogger
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void error(LogPair... logPairs)
|
|
||||||
{
|
|
||||||
logger.warn(() -> makeJsonString(null, null, logPairs));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -378,7 +378,7 @@ public class JoinsContext
|
|||||||
{
|
{
|
||||||
securityFieldTableAlias = matchedQueryJoin.getJoinTableOrItsAlias();
|
securityFieldTableAlias = matchedQueryJoin.getJoinTableOrItsAlias();
|
||||||
}
|
}
|
||||||
tmpTable = instance.getTable(aliasToTableNameMap.getOrDefault(securityFieldTableAlias, securityFieldTableAlias));
|
tmpTable = instance.getTable(securityFieldTableAlias);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// set the baseTableOrAlias for the next iteration to be this join's joinTableOrAlias //
|
// set the baseTableOrAlias for the next iteration to be this join's joinTableOrAlias //
|
||||||
@ -466,8 +466,8 @@ public class JoinsContext
|
|||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
|
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||||
boolean haveAllAccessKey = false;
|
boolean haveAllAccessKey = false;
|
||||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
|
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -1118,7 +1118,7 @@ public class JoinsContext
|
|||||||
if(useExposedJoins)
|
if(useExposedJoins)
|
||||||
{
|
{
|
||||||
QTableMetaData mainTable = QContext.getQInstance().getTable(mainTableName);
|
QTableMetaData mainTable = QContext.getQInstance().getTable(mainTableName);
|
||||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(mainTable.getExposedJoins()))
|
for(ExposedJoin exposedJoin : mainTable.getExposedJoins())
|
||||||
{
|
{
|
||||||
if(exposedJoin.getJoinTable().equals(joinTableName))
|
if(exposedJoin.getJoinTable().equals(joinTableName))
|
||||||
{
|
{
|
||||||
@ -1159,7 +1159,6 @@ public class JoinsContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -238,28 +237,6 @@ public class QQueryFilter implements Serializable, Cloneable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** fluent method to add a new criteria
|
|
||||||
*******************************************************************************/
|
|
||||||
public QQueryFilter withCriteria(String fieldName, QCriteriaOperator operator, Collection<? extends Serializable> values)
|
|
||||||
{
|
|
||||||
addCriteria(new QFilterCriteria(fieldName, operator, values));
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** fluent method to add a new criteria
|
|
||||||
*******************************************************************************/
|
|
||||||
public QQueryFilter withCriteria(String fieldName, QCriteriaOperator operator, Serializable... values)
|
|
||||||
{
|
|
||||||
addCriteria(new QFilterCriteria(fieldName, operator, values));
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -56,7 +56,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|||||||
** JoinsContext is constructed before executing a query, and not meant to be set
|
** JoinsContext is constructed before executing a query, and not meant to be set
|
||||||
** by users.
|
** by users.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QueryJoin implements Cloneable
|
public class QueryJoin
|
||||||
{
|
{
|
||||||
private String baseTableOrAlias;
|
private String baseTableOrAlias;
|
||||||
private String joinTable;
|
private String joinTable;
|
||||||
@ -69,40 +69,6 @@ public class QueryJoin implements Cloneable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public QueryJoin clone()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryJoin clone = (QueryJoin) super.clone();
|
|
||||||
|
|
||||||
if(joinMetaData != null)
|
|
||||||
{
|
|
||||||
clone.joinMetaData = joinMetaData.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(securityCriteria != null)
|
|
||||||
{
|
|
||||||
clone.securityCriteria = new ArrayList<>();
|
|
||||||
for(QFilterCriteria securityCriterion : securityCriteria)
|
|
||||||
{
|
|
||||||
clone.securityCriteria.add(securityCriterion.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
catch(CloneNotSupportedException e)
|
|
||||||
{
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** define the types of joins - INNER, LEFT, RIGHT, or FULL.
|
** define the types of joins - INNER, LEFT, RIGHT, or FULL.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -53,6 +53,8 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
|||||||
private String variantOptionsTableUsernameField;
|
private String variantOptionsTableUsernameField;
|
||||||
private String variantOptionsTablePasswordField;
|
private String variantOptionsTablePasswordField;
|
||||||
private String variantOptionsTableApiKeyField;
|
private String variantOptionsTableApiKeyField;
|
||||||
|
private String variantOptionsTableClientIdField;
|
||||||
|
private String variantOptionsTableClientSecretField;
|
||||||
private String variantOptionsTableName;
|
private String variantOptionsTableName;
|
||||||
|
|
||||||
// todo - at some point, we may want to apply this to secret properties on subclasses?
|
// todo - at some point, we may want to apply this to secret properties on subclasses?
|
||||||
@ -648,4 +650,66 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
|||||||
{
|
{
|
||||||
qInstance.addBackend(this);
|
qInstance.addBackend(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for variantOptionsTableClientIdField
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getVariantOptionsTableClientIdField()
|
||||||
|
{
|
||||||
|
return (this.variantOptionsTableClientIdField);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for variantOptionsTableClientIdField
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
|
||||||
|
{
|
||||||
|
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for variantOptionsTableClientIdField
|
||||||
|
*******************************************************************************/
|
||||||
|
public QBackendMetaData withVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
|
||||||
|
{
|
||||||
|
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for variantOptionsTableClientSecretField
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getVariantOptionsTableClientSecretField()
|
||||||
|
{
|
||||||
|
return (this.variantOptionsTableClientSecretField);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for variantOptionsTableClientSecretField
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
|
||||||
|
{
|
||||||
|
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for variantOptionsTableClientSecretField
|
||||||
|
*******************************************************************************/
|
||||||
|
public QBackendMetaData withVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
|
||||||
|
{
|
||||||
|
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.joins;
|
|||||||
** Specification for (at least part of) how two tables join together - e.g.,
|
** Specification for (at least part of) how two tables join together - e.g.,
|
||||||
** leftField = rightField. Used as part of a list in a QJoinMetaData.
|
** leftField = rightField. Used as part of a list in a QJoinMetaData.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class JoinOn implements Cloneable
|
public class JoinOn
|
||||||
{
|
{
|
||||||
private String leftField;
|
private String leftField;
|
||||||
private String rightField;
|
private String rightField;
|
||||||
@ -131,22 +131,4 @@ public class JoinOn implements Cloneable
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public JoinOn clone()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JoinOn clone = (JoinOn) super.clone();
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
catch(CloneNotSupportedException e)
|
|
||||||
{
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Definition of how 2 tables join together within a QQQ Instance.
|
** Definition of how 2 tables join together within a QQQ Instance.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QJoinMetaData implements TopLevelMetaDataInterface, Cloneable
|
public class QJoinMetaData implements TopLevelMetaDataInterface
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private JoinType type;
|
private JoinType type;
|
||||||
@ -62,44 +62,6 @@ public class QJoinMetaData implements TopLevelMetaDataInterface, Cloneable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Override
|
|
||||||
public QJoinMetaData clone()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QJoinMetaData clone = (QJoinMetaData) super.clone();
|
|
||||||
|
|
||||||
if(joinOns != null)
|
|
||||||
{
|
|
||||||
clone.joinOns = new ArrayList<>();
|
|
||||||
for(JoinOn joinOn : joinOns)
|
|
||||||
{
|
|
||||||
clone.joinOns.add(joinOn.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(orderBys != null)
|
|
||||||
{
|
|
||||||
clone.orderBys = new ArrayList<>();
|
|
||||||
for(QFilterOrderBy orderBy : orderBys)
|
|
||||||
{
|
|
||||||
clone.orderBys.add(orderBy.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
catch(CloneNotSupportedException e)
|
|
||||||
{
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for name
|
** Getter for name
|
||||||
**
|
**
|
||||||
|
@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.processes.implementations.columnstats;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
|
||||||
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.metadata.fields.QFieldMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Utility for verifying that the ColumnStats process works for all fields,
|
|
||||||
** on all tables, and all exposed joins.
|
|
||||||
**
|
|
||||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
|
||||||
** validation.
|
|
||||||
*******************************************************************************/
|
|
||||||
public class ColumnStatsFullInstanceVerifier
|
|
||||||
{
|
|
||||||
private static final QLogger LOG = QLogger.getLogger(ColumnStatsFullInstanceVerifier.class);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void verify(Collection<QTableMetaData> tables) throws QException
|
|
||||||
{
|
|
||||||
Map<Pair<String, String>, Exception> caughtExceptions = new LinkedHashMap<>();
|
|
||||||
for(QTableMetaData table : tables)
|
|
||||||
{
|
|
||||||
if(table.isCapabilityEnabled(QContext.getQInstance().getBackendForTable(table.getName()), Capability.QUERY_STATS))
|
|
||||||
{
|
|
||||||
LOG.info("Verifying ColumnStats on table", logPair("tableName", table.getName()));
|
|
||||||
for(QFieldMetaData field : table.getFields().values())
|
|
||||||
{
|
|
||||||
runColumnStats(table.getName(), field.getName(), caughtExceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
|
||||||
{
|
|
||||||
QTableMetaData joinTable = QContext.getQInstance().getTable(exposedJoin.getJoinTable());
|
|
||||||
for(QFieldMetaData field : joinTable.getFields().values())
|
|
||||||
{
|
|
||||||
runColumnStats(table.getName(), joinTable.getName() + "." + field.getName(), caughtExceptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// log out an exceptions caught
|
|
||||||
if(!caughtExceptions.isEmpty())
|
|
||||||
{
|
|
||||||
for(Map.Entry<Pair<String, String>, Exception> entry : caughtExceptions.entrySet())
|
|
||||||
{
|
|
||||||
LOG.info("Caught an exception verifying column stats", entry.getValue(), logPair("tableName", entry.getKey().getA()), logPair("fieldName", entry.getKey().getB()));
|
|
||||||
}
|
|
||||||
throw (new QException("Column Status Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private void runColumnStats(String tableName, String fieldName, Map<Pair<String, String>, Exception> caughtExceptions) throws QException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RunBackendStepInput input = new RunBackendStepInput();
|
|
||||||
input.addValue("tableName", tableName);
|
|
||||||
input.addValue("fieldName", fieldName);
|
|
||||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
|
||||||
new ColumnStatsStep().run(input, output);
|
|
||||||
}
|
|
||||||
catch(QException e)
|
|
||||||
{
|
|
||||||
Throwable rootException = ExceptionUtils.getRootException(e);
|
|
||||||
if(rootException instanceof QException && rootException.getMessage().contains("not supported for this field's data type"))
|
|
||||||
{
|
|
||||||
////////////////////////////////////////////////
|
|
||||||
// ignore this exception, it's kinda expected //
|
|
||||||
////////////////////////////////////////////////
|
|
||||||
LOG.debug("Caught an expected-exception in column stats", e, logPair("tableName", tableName), logPair("fieldName", fieldName));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
caughtExceptions.put(Pair.of(tableName, fieldName), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -136,11 +136,29 @@ public class ColumnStatsStep implements BackendStep
|
|||||||
filter = new QQueryFilter();
|
filter = new QQueryFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
QueryJoin queryJoin = null;
|
||||||
|
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||||
FieldAndQueryJoin fieldAndQueryJoin = getFieldAndQueryJoin(table, fieldName);
|
QFieldMetaData field = null;
|
||||||
QFieldMetaData field = fieldAndQueryJoin.field();
|
if(fieldName.contains("."))
|
||||||
QueryJoin queryJoin = fieldAndQueryJoin.queryJoin();
|
{
|
||||||
|
String[] parts = fieldName.split("\\.", 2);
|
||||||
|
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
||||||
|
{
|
||||||
|
if(exposedJoin.getJoinTable().equals(parts[0]))
|
||||||
|
{
|
||||||
|
field = QContext.getQInstance().getTable(exposedJoin.getJoinTable()).getField(parts[1]);
|
||||||
|
queryJoin = new QueryJoin()
|
||||||
|
.withJoinTable(exposedJoin.getJoinTable())
|
||||||
|
.withSelect(true)
|
||||||
|
.withType(QueryJoin.Type.INNER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
field = table.getField(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
if(field == null)
|
if(field == null)
|
||||||
{
|
{
|
||||||
@ -195,7 +213,7 @@ public class ColumnStatsStep implements BackendStep
|
|||||||
filter.withOrderBy(new QFilterOrderByAggregate(aggregate, false));
|
filter.withOrderBy(new QFilterOrderByAggregate(aggregate, false));
|
||||||
filter.withOrderBy(new QFilterOrderByGroupBy(groupBy));
|
filter.withOrderBy(new QFilterOrderByGroupBy(groupBy));
|
||||||
|
|
||||||
Integer limit = 1000;
|
Integer limit = 1000; // too big?
|
||||||
AggregateInput aggregateInput = new AggregateInput();
|
AggregateInput aggregateInput = new AggregateInput();
|
||||||
aggregateInput.withAggregate(aggregate);
|
aggregateInput.withAggregate(aggregate);
|
||||||
aggregateInput.withGroupBy(groupBy);
|
aggregateInput.withGroupBy(groupBy);
|
||||||
@ -205,11 +223,7 @@ public class ColumnStatsStep implements BackendStep
|
|||||||
|
|
||||||
if(queryJoin != null)
|
if(queryJoin != null)
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
aggregateInput.withQueryJoin(queryJoin);
|
||||||
// re-construct this queryJoin object - just because, the JoinsContext edits the previous one, so we can make some failing-joins otherwise... //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
fieldAndQueryJoin = getFieldAndQueryJoin(table, fieldName);
|
|
||||||
aggregateInput.withQueryJoin(fieldAndQueryJoin.queryJoin());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AggregateOutput aggregateOutput = new AggregateAction().execute(aggregateInput);
|
AggregateOutput aggregateOutput = new AggregateAction().execute(aggregateInput);
|
||||||
@ -224,7 +238,7 @@ public class ColumnStatsStep implements BackendStep
|
|||||||
value = Instant.parse(value + ":00:00Z");
|
value = Instant.parse(value + ":00:00Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer count = ValueUtils.getValueAsInteger(result.getAggregateValue(aggregate));
|
Integer count = ValueUtils.getValueAsInteger(result.getAggregateValue(aggregate));
|
||||||
valueCounts.add(new QRecord().withValue(fieldName, value).withValue("count", count));
|
valueCounts.add(new QRecord().withValue(fieldName, value).withValue("count", count));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,43 +526,4 @@ public class ColumnStatsStep implements BackendStep
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private FieldAndQueryJoin getFieldAndQueryJoin(QTableMetaData table, String fieldName)
|
|
||||||
{
|
|
||||||
QFieldMetaData field = null;
|
|
||||||
QueryJoin queryJoin = null;
|
|
||||||
if(fieldName.contains("."))
|
|
||||||
{
|
|
||||||
String[] parts = fieldName.split("\\.", 2);
|
|
||||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
|
||||||
{
|
|
||||||
if(exposedJoin.getJoinTable().equals(parts[0]))
|
|
||||||
{
|
|
||||||
field = QContext.getQInstance().getTable(exposedJoin.getJoinTable()).getField(parts[1]);
|
|
||||||
queryJoin = new QueryJoin()
|
|
||||||
.withJoinTable(exposedJoin.getJoinTable())
|
|
||||||
.withSelect(true)
|
|
||||||
.withType(QueryJoin.Type.INNER);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
field = table.getField(fieldName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (new FieldAndQueryJoin(field, queryJoin));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private record FieldAndQueryJoin(QFieldMetaData field, QueryJoin queryJoin) {}
|
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,6 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
|||||||
.withRenderedReportStatusId(RenderedReportStatus.RUNNING.getId())
|
.withRenderedReportStatusId(RenderedReportStatus.RUNNING.getId())
|
||||||
.withReportFormat(ReportFormatPossibleValueEnum.valueOf(reportFormat.name()).getPossibleValueId())
|
.withReportFormat(ReportFormatPossibleValueEnum.valueOf(reportFormat.name()).getPossibleValueId())
|
||||||
)).getRecords().get(0);
|
)).getRecords().get(0);
|
||||||
runBackendStepOutput.addValue("renderedReportId", renderedReportRecord.getValue("id"));
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// convert the report record to report meta-data, which the GenerateReportAction works on //
|
// convert the report record to report meta-data, which the GenerateReportAction works on //
|
||||||
|
@ -1,258 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.processes.implementations.savedreports;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
|
||||||
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.reporting.ReportFormat;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
|
||||||
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.tables.Capability;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReport;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.ReportColumns;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Utility for verifying that the RenderReports process works for all fields,
|
|
||||||
** on all tables, and all exposed joins.
|
|
||||||
**
|
|
||||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
|
||||||
** validation.
|
|
||||||
*******************************************************************************/
|
|
||||||
public class ReportsFullInstanceVerifier
|
|
||||||
{
|
|
||||||
private static final QLogger LOG = QLogger.getLogger(ReportsFullInstanceVerifier.class);
|
|
||||||
|
|
||||||
private boolean removeRenderedReports = true;
|
|
||||||
private boolean filterForAtMostOneRowPerReport = true;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void verify(Collection<QTableMetaData> tables, String storageTableName) throws QException
|
|
||||||
{
|
|
||||||
Map<Pair<String, String>, Exception> caughtExceptions = new LinkedHashMap<>();
|
|
||||||
for(QTableMetaData table : tables)
|
|
||||||
{
|
|
||||||
if(table.isCapabilityEnabled(QContext.getQInstance().getBackendForTable(table.getName()), Capability.TABLE_QUERY))
|
|
||||||
{
|
|
||||||
LOG.info("Verifying Reports on table", logPair("tableName", table.getName()));
|
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
// run the table by itself (no join fields) //
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
runReport(table.getName(), Collections.emptyList(), "main-table-only", caughtExceptions, storageTableName);
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
// run once w/ the fields from each exposed join //
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
|
||||||
{
|
|
||||||
runReport(table.getName(), List.of(exposedJoin), "join-" + exposedJoin.getLabel(), caughtExceptions, storageTableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
// run w/ all exposed joins (if there are any) //
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
if(CollectionUtils.nullSafeHasContents(table.getExposedJoins()))
|
|
||||||
{
|
|
||||||
runReport(table.getName(), table.getExposedJoins(), "all-joins", caughtExceptions, storageTableName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// log out an exceptions caught //
|
|
||||||
//////////////////////////////////
|
|
||||||
if(!caughtExceptions.isEmpty())
|
|
||||||
{
|
|
||||||
for(Map.Entry<Pair<String, String>, Exception> entry : caughtExceptions.entrySet())
|
|
||||||
{
|
|
||||||
LOG.info("Caught an exception verifying reports", entry.getValue(), logPair("tableName", entry.getKey().getA()), logPair("fieldName", entry.getKey().getB()));
|
|
||||||
}
|
|
||||||
throw (new QException("Reports Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private void runReport(String tableName, List<ExposedJoin> exposedJoinList, String description, Map<Pair<String, String>, Exception> caughtExceptions, String storageTableName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// build the list of reports to include in the column - starting with all fields in the table //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
ReportColumns reportColumns = new ReportColumns();
|
|
||||||
for(QFieldMetaData field : QContext.getQInstance().getTable(tableName).getFields().values())
|
|
||||||
{
|
|
||||||
reportColumns.withColumn(field.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
// add all fields from all exposed joins as well //
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(exposedJoinList))
|
|
||||||
{
|
|
||||||
QTableMetaData joinTable = QContext.getQInstance().getTable(exposedJoin.getJoinTable());
|
|
||||||
for(QFieldMetaData field : joinTable.getFields().values())
|
|
||||||
{
|
|
||||||
reportColumns.withColumn(joinTable.getName() + "." + field.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQueryFilter queryFilter = new QQueryFilter();
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// if caller is okay with a filter that should limit the report to a small number of rows (could be more than 1 for to-many joins), then do so //
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
if(filterForAtMostOneRowPerReport)
|
|
||||||
{
|
|
||||||
queryFilter.withCriteria(QContext.getQInstance().getTable(tableName).getPrimaryKeyField(), QCriteriaOperator.EQUALS, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// insert a saved report record //
|
|
||||||
//////////////////////////////////
|
|
||||||
SavedReport savedReport = new SavedReport();
|
|
||||||
savedReport.setTableName(tableName);
|
|
||||||
savedReport.setLabel("Test " + tableName + " " + description);
|
|
||||||
savedReport.setColumnsJson(JsonUtils.toJson(reportColumns));
|
|
||||||
savedReport.setQueryFilterJson(JsonUtils.toJson(queryFilter));
|
|
||||||
List<QRecord> reportRecordList = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(savedReport)).getRecords();
|
|
||||||
|
|
||||||
///////////////////////
|
|
||||||
// render the report //
|
|
||||||
///////////////////////
|
|
||||||
RunBackendStepInput input = new RunBackendStepInput();
|
|
||||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
|
||||||
|
|
||||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT, ReportFormat.CSV.name());
|
|
||||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME, storageTableName);
|
|
||||||
input.setRecords(reportRecordList);
|
|
||||||
|
|
||||||
new RenderSavedReportExecuteStep().run(input, output);
|
|
||||||
|
|
||||||
//////////////////////////////////////////
|
|
||||||
// clean up the report, if so requested //
|
|
||||||
//////////////////////////////////////////
|
|
||||||
if(removeRenderedReports)
|
|
||||||
{
|
|
||||||
new DeleteAction().execute(new DeleteInput(RenderedReport.TABLE_NAME).withPrimaryKey(output.getValue("renderedReportId")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(QException e)
|
|
||||||
{
|
|
||||||
caughtExceptions.put(Pair.of(tableName, description), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for removeRenderedReports
|
|
||||||
*******************************************************************************/
|
|
||||||
public boolean getRemoveRenderedReports()
|
|
||||||
{
|
|
||||||
return (this.removeRenderedReports);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for removeRenderedReports
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setRemoveRenderedReports(boolean removeRenderedReports)
|
|
||||||
{
|
|
||||||
this.removeRenderedReports = removeRenderedReports;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for removeRenderedReports
|
|
||||||
*******************************************************************************/
|
|
||||||
public ReportsFullInstanceVerifier withRemoveRenderedReports(boolean removeRenderedReports)
|
|
||||||
{
|
|
||||||
this.removeRenderedReports = removeRenderedReports;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for filterForAtMostOneRowPerReport
|
|
||||||
*******************************************************************************/
|
|
||||||
public boolean getFilterForAtMostOneRowPerReport()
|
|
||||||
{
|
|
||||||
return (this.filterForAtMostOneRowPerReport);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for filterForAtMostOneRowPerReport
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setFilterForAtMostOneRowPerReport(boolean filterForAtMostOneRowPerReport)
|
|
||||||
{
|
|
||||||
this.filterForAtMostOneRowPerReport = filterForAtMostOneRowPerReport;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for filterForAtMostOneRowPerReport
|
|
||||||
*******************************************************************************/
|
|
||||||
public ReportsFullInstanceVerifier withFilterForAtMostOneRowPerReport(boolean filterForAtMostOneRowPerReport)
|
|
||||||
{
|
|
||||||
this.filterForAtMostOneRowPerReport = filterForAtMostOneRowPerReport;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.processes.implementations.savedreports;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
|
||||||
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.reporting.ReportFormat;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReport;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Utility for verifying that the RenderReports process works for all report
|
|
||||||
** records stored in the saved reports table.
|
|
||||||
**
|
|
||||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
|
||||||
** validation.
|
|
||||||
*******************************************************************************/
|
|
||||||
public class SavedReportsTableFullVerifier
|
|
||||||
{
|
|
||||||
private static final QLogger LOG = QLogger.getLogger(SavedReportsTableFullVerifier.class);
|
|
||||||
|
|
||||||
private boolean removeRenderedReports = true;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void verify(List<QRecord> savedReportRecordList, String storageTableName) throws QException
|
|
||||||
{
|
|
||||||
Map<Integer, Exception> caughtExceptions = new LinkedHashMap<>();
|
|
||||||
for(QRecord report : savedReportRecordList)
|
|
||||||
{
|
|
||||||
runReport(report, caughtExceptions, storageTableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// log out an exceptions caught //
|
|
||||||
//////////////////////////////////
|
|
||||||
if(!caughtExceptions.isEmpty())
|
|
||||||
{
|
|
||||||
for(Map.Entry<Integer, Exception> entry : caughtExceptions.entrySet())
|
|
||||||
{
|
|
||||||
LOG.info("Caught an exception verifying saved reports", entry.getValue(), logPair("savdReportId", entry.getKey()));
|
|
||||||
}
|
|
||||||
throw (new QException("Saved Reports Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private void runReport(QRecord savedReport, Map<Integer, Exception> caughtExceptions, String storageTableName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
///////////////////////
|
|
||||||
// render the report //
|
|
||||||
///////////////////////
|
|
||||||
RunBackendStepInput input = new RunBackendStepInput();
|
|
||||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
|
||||||
|
|
||||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT, ReportFormat.XLSX.name());
|
|
||||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME, storageTableName);
|
|
||||||
input.setRecords(List.of(savedReport));
|
|
||||||
|
|
||||||
new RenderSavedReportExecuteStep().run(input, output);
|
|
||||||
Exception exception = output.getException();
|
|
||||||
if(exception != null)
|
|
||||||
{
|
|
||||||
throw (exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////
|
|
||||||
// clean up the report, if so requested //
|
|
||||||
//////////////////////////////////////////
|
|
||||||
if(removeRenderedReports)
|
|
||||||
{
|
|
||||||
new DeleteAction().execute(new DeleteInput(RenderedReport.TABLE_NAME).withPrimaryKey(output.getValue("renderedReportId")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
caughtExceptions.put(savedReport.getValueInteger("id"), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for removeRenderedReports
|
|
||||||
*******************************************************************************/
|
|
||||||
public boolean getRemoveRenderedReports()
|
|
||||||
{
|
|
||||||
return (this.removeRenderedReports);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for removeRenderedReports
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setRemoveRenderedReports(boolean removeRenderedReports)
|
|
||||||
{
|
|
||||||
this.removeRenderedReports = removeRenderedReports;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for removeRenderedReports
|
|
||||||
*******************************************************************************/
|
|
||||||
public SavedReportsTableFullVerifier withRemoveRenderedReports(boolean removeRenderedReports)
|
|
||||||
{
|
|
||||||
this.removeRenderedReports = removeRenderedReports;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Unit test for ExportsFullInstanceVerifier
|
|
||||||
*******************************************************************************/
|
|
||||||
class ExportsFullInstanceVerifierTest extends BaseTest
|
|
||||||
{
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void test() throws QException
|
|
||||||
{
|
|
||||||
new ExportsFullInstanceVerifier().verify(QContext.getQInstance().getTables().values());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.processes.implementations.columnstats;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Unit test for ColumnStatsFullInstanceVerifier
|
|
||||||
*******************************************************************************/
|
|
||||||
class ColumnStatsFullInstanceVerifierTest extends BaseTest
|
|
||||||
{
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void test() throws QException
|
|
||||||
{
|
|
||||||
new ColumnStatsFullInstanceVerifier().verify(List.of(QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.processes.implementations.savedreports;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Unit test for ReportsFullInstanceVerifier
|
|
||||||
*******************************************************************************/
|
|
||||||
class ReportsFullInstanceVerifierTest extends BaseTest
|
|
||||||
{
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@BeforeEach
|
|
||||||
void beforeEach() throws Exception
|
|
||||||
{
|
|
||||||
new SavedReportsMetaDataProvider().defineAll(QContext.getQInstance(), TestUtils.MEMORY_BACKEND_NAME, TestUtils.MEMORY_BACKEND_NAME, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void test() throws QException
|
|
||||||
{
|
|
||||||
new ReportsFullInstanceVerifier().verify(List.of(QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)), SavedReportsMetaDataProvider.REPORT_STORAGE_TABLE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.processes.implementations.savedreports;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportActionTest;
|
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.ReportColumns;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Unit test for SavedReportsTableFullVerifier
|
|
||||||
*******************************************************************************/
|
|
||||||
class SavedReportsTableFullVerifierTest extends BaseTest
|
|
||||||
{
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@BeforeEach
|
|
||||||
void beforeEach() throws Exception
|
|
||||||
{
|
|
||||||
new SavedReportsMetaDataProvider().defineAll(QContext.getQInstance(), TestUtils.MEMORY_BACKEND_NAME, TestUtils.MEMORY_BACKEND_NAME, null);
|
|
||||||
GenerateReportActionTest.insertPersonRecords(QContext.getQInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void test() throws QException
|
|
||||||
{
|
|
||||||
ReportColumns reportColumns = new ReportColumns();
|
|
||||||
reportColumns.withColumn("id");
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// insert a saved report record //
|
|
||||||
//////////////////////////////////
|
|
||||||
SavedReport savedReport = new SavedReport();
|
|
||||||
savedReport.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
|
||||||
savedReport.setLabel("Test");
|
|
||||||
savedReport.setColumnsJson(JsonUtils.toJson(reportColumns));
|
|
||||||
savedReport.setQueryFilterJson(JsonUtils.toJson(new QQueryFilter()));
|
|
||||||
List<QRecord> reportRecordList = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(savedReport)).getRecords();
|
|
||||||
|
|
||||||
SavedReportsTableFullVerifier savedReportsTableFullVerifier = new SavedReportsTableFullVerifier();
|
|
||||||
savedReportsTableFullVerifier.verify(reportRecordList, SavedReportsMetaDataProvider.REPORT_STORAGE_TABLE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -35,6 +35,7 @@ import java.util.Base64;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
@ -64,6 +65,7 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
|
|||||||
import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage;
|
import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
@ -87,6 +89,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
|
|||||||
import org.apache.http.client.methods.HttpDelete;
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPatch;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.client.methods.HttpPut;
|
import org.apache.http.client.methods.HttpPut;
|
||||||
import org.apache.http.client.methods.HttpRequestBase;
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
@ -117,8 +120,37 @@ public class BaseAPIActionUtil
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** enum of which HTTP Method the backend uses for Updates.
|
||||||
|
***************************************************************************/
|
||||||
public enum UpdateHttpMethod
|
public enum UpdateHttpMethod
|
||||||
{PUT, POST}
|
{
|
||||||
|
PUT(HttpPut::new),
|
||||||
|
POST(HttpPost::new),
|
||||||
|
PATCH(HttpPatch::new);
|
||||||
|
|
||||||
|
private Supplier<HttpEntityEnclosingRequestBase> httpEntitySupplier;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
UpdateHttpMethod(Supplier<HttpEntityEnclosingRequestBase> httpEnttySupplier)
|
||||||
|
{
|
||||||
|
this.httpEntitySupplier = httpEnttySupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
public HttpEntityEnclosingRequestBase newRequest()
|
||||||
|
{
|
||||||
|
return (this.httpEntitySupplier.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -347,7 +379,9 @@ public class BaseAPIActionUtil
|
|||||||
{
|
{
|
||||||
String paramString = buildQueryStringForUpdate(table, recordList);
|
String paramString = buildQueryStringForUpdate(table, recordList);
|
||||||
String url = buildTableUrl(table) + paramString;
|
String url = buildTableUrl(table) + paramString;
|
||||||
HttpEntityEnclosingRequestBase request = getUpdateMethod().equals(UpdateHttpMethod.PUT) ? new HttpPut(url) : new HttpPost(url);
|
HttpEntityEnclosingRequestBase request = getUpdateMethod().newRequest();
|
||||||
|
|
||||||
|
request.setURI(new URI(url));
|
||||||
request.setEntity(recordsToEntity(table, recordList));
|
request.setEntity(recordsToEntity(table, recordList));
|
||||||
|
|
||||||
QHttpResponse response = makeRequest(table, request);
|
QHttpResponse response = makeRequest(table, request);
|
||||||
@ -685,54 +719,19 @@ public class BaseAPIActionUtil
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setupAuthorizationInRequest(HttpRequestBase request) throws QException
|
public void setupAuthorizationInRequest(HttpRequestBase request) throws QException
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
// if backend specifies that it uses variants, look for that data in the session //
|
// update the request based on the authorization type being used //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
if(backendMetaData.getUsesVariants())
|
|
||||||
{
|
|
||||||
QSession session = QContext.getQSession();
|
|
||||||
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(backendMetaData.getVariantOptionsTableTypeValue()))
|
|
||||||
{
|
|
||||||
throw (new QException("Could not find Backend Variant information for Backend '" + backendMetaData.getName() + "'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Serializable variantId = session.getBackendVariants().get(backendMetaData.getVariantOptionsTableTypeValue());
|
|
||||||
GetInput getInput = new GetInput();
|
|
||||||
getInput.setShouldMaskPasswords(false);
|
|
||||||
getInput.setTableName(backendMetaData.getVariantOptionsTableName());
|
|
||||||
getInput.setPrimaryKey(variantId);
|
|
||||||
GetOutput getOutput = new GetAction().execute(getInput);
|
|
||||||
|
|
||||||
QRecord record = getOutput.getRecord();
|
|
||||||
if(record == null)
|
|
||||||
{
|
|
||||||
throw (new QException("Could not find Backend Variant in table " + backendMetaData.getVariantOptionsTableName() + " with id '" + variantId + "'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(backendMetaData.getAuthorizationType().equals(AuthorizationType.BASIC_AUTH_USERNAME_PASSWORD))
|
|
||||||
{
|
|
||||||
request.setHeader("Authorization", getBasicAuthenticationHeader(record.getValueString(backendMetaData.getVariantOptionsTableUsernameField()), record.getValueString(backendMetaData.getVariantOptionsTablePasswordField())));
|
|
||||||
}
|
|
||||||
else if(backendMetaData.getAuthorizationType().equals(AuthorizationType.API_KEY_HEADER))
|
|
||||||
{
|
|
||||||
request.setHeader("API-Key", record.getValueString(backendMetaData.getVariantOptionsTableApiKeyField()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw (new IllegalArgumentException("Unexpected variant authorization type specified: " + backendMetaData.getAuthorizationType()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// if not using variants, the authorization data will be in the backend meta data object //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
switch(backendMetaData.getAuthorizationType())
|
switch(backendMetaData.getAuthorizationType())
|
||||||
{
|
{
|
||||||
case BASIC_AUTH_API_KEY -> request.setHeader("Authorization", getBasicAuthenticationHeader(backendMetaData.getApiKey()));
|
case BASIC_AUTH_API_KEY -> request.setHeader("Authorization", getBasicAuthenticationHeader(getApiKey()));
|
||||||
case BASIC_AUTH_USERNAME_PASSWORD -> request.setHeader("Authorization", getBasicAuthenticationHeader(backendMetaData.getUsername(), backendMetaData.getPassword()));
|
case BASIC_AUTH_USERNAME_PASSWORD ->
|
||||||
case API_KEY_HEADER -> request.setHeader("API-Key", backendMetaData.getApiKey());
|
{
|
||||||
case API_TOKEN -> request.setHeader("Authorization", "Token " + backendMetaData.getApiKey());
|
Pair<String, String> usernameAndPassword = getUsernameAndPassword();
|
||||||
|
request.setHeader("Authorization", getBasicAuthenticationHeader(usernameAndPassword.getA(), usernameAndPassword.getB()));
|
||||||
|
}
|
||||||
|
case API_KEY_HEADER -> request.setHeader("API-Key", getApiKey());
|
||||||
|
case API_TOKEN -> request.setHeader("Authorization", "Token " + getApiKey());
|
||||||
case OAUTH2 -> request.setHeader("Authorization", "Bearer " + getOAuth2Token());
|
case OAUTH2 -> request.setHeader("Authorization", "Bearer " + getOAuth2Token());
|
||||||
case API_KEY_QUERY_PARAM ->
|
case API_KEY_QUERY_PARAM ->
|
||||||
{
|
{
|
||||||
@ -740,7 +739,7 @@ public class BaseAPIActionUtil
|
|||||||
{
|
{
|
||||||
String uri = request.getURI().toString();
|
String uri = request.getURI().toString();
|
||||||
uri += (uri.contains("?") ? "&" : "?");
|
uri += (uri.contains("?") ? "&" : "?");
|
||||||
uri += backendMetaData.getApiKeyQueryParamName() + "=" + backendMetaData.getApiKey();
|
uri += backendMetaData.getApiKeyQueryParamName() + "=" + getApiKey();
|
||||||
request.setURI(new URI(uri));
|
request.setURI(new URI(uri));
|
||||||
}
|
}
|
||||||
catch(URISyntaxException e)
|
catch(URISyntaxException e)
|
||||||
@ -748,49 +747,113 @@ public class BaseAPIActionUtil
|
|||||||
throw (new QException("Error setting authorization query parameter", e));
|
throw (new QException("Error setting authorization query parameter", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case CUSTOM ->
|
case CUSTOM -> handleCustomAuthorization(request);
|
||||||
{
|
|
||||||
handleCustomAuthorization(request);
|
|
||||||
}
|
|
||||||
default -> throw new IllegalArgumentException("Unexpected authorization type: " + backendMetaData.getAuthorizationType());
|
default -> throw new IllegalArgumentException("Unexpected authorization type: " + backendMetaData.getAuthorizationType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
protected String getApiKey() throws QException
|
||||||
|
{
|
||||||
|
if(backendMetaData.getUsesVariants())
|
||||||
|
{
|
||||||
|
QRecord record = getVariantRecord();
|
||||||
|
return (record.getValueString(backendMetaData.getVariantOptionsTableApiKeyField()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (backendMetaData.getApiKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
protected Pair<String, String> getUsernameAndPassword() throws QException
|
||||||
|
{
|
||||||
|
if(backendMetaData.getUsesVariants())
|
||||||
|
{
|
||||||
|
QRecord record = getVariantRecord();
|
||||||
|
return (Pair.of(record.getValueString(backendMetaData.getVariantOptionsTableUsernameField()), record.getValueString(backendMetaData.getVariantOptionsTablePasswordField())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Pair.of(backendMetaData.getUsername(), backendMetaData.getPassword()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** For backends that use variants, look up the variant record (in theory, based
|
||||||
|
** on an id in the session's backend variants map, then fetched from the backend's
|
||||||
|
** variant options table.
|
||||||
|
*******************************************************************************/
|
||||||
|
protected QRecord getVariantRecord() throws QException
|
||||||
|
{
|
||||||
|
Serializable variantId = getVariantId();
|
||||||
|
GetInput getInput = new GetInput();
|
||||||
|
getInput.setShouldMaskPasswords(false);
|
||||||
|
getInput.setTableName(backendMetaData.getVariantOptionsTableName());
|
||||||
|
getInput.setPrimaryKey(variantId);
|
||||||
|
GetOutput getOutput = new GetAction().execute(getInput);
|
||||||
|
|
||||||
|
QRecord record = getOutput.getRecord();
|
||||||
|
if(record == null)
|
||||||
|
{
|
||||||
|
throw (new QException("Could not find Backend Variant in table " + backendMetaData.getVariantOptionsTableName() + " with id '" + variantId + "'"));
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get the variant id from the session for the backend.
|
||||||
|
*******************************************************************************/
|
||||||
|
protected Serializable getVariantId() throws QException
|
||||||
|
{
|
||||||
|
QSession session = QContext.getQSession();
|
||||||
|
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(backendMetaData.getVariantOptionsTableTypeValue()))
|
||||||
|
{
|
||||||
|
throw (new QException("Could not find Backend Variant information for Backend '" + backendMetaData.getName() + "'"));
|
||||||
|
}
|
||||||
|
Serializable variantId = session.getBackendVariants().get(backendMetaData.getVariantOptionsTableTypeValue());
|
||||||
|
return variantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public String getOAuth2Token() throws OAuthCredentialsException
|
public String getOAuth2Token() throws OAuthCredentialsException, QException
|
||||||
{
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// define the key that will be used in the backend's customValues map, to stash the access token. //
|
||||||
|
// for non-variant backends, this is just a constant string. But for variant-backends, append the variantId to it. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
String accessTokenKey = "accessToken";
|
||||||
|
if(backendMetaData.getUsesVariants())
|
||||||
|
{
|
||||||
|
Serializable variantId = getVariantId();
|
||||||
|
accessTokenKey = accessTokenKey + ":" + variantId;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// check for the access token in the backend meta data. if it's not there, then issue a request for a token. //
|
// check for the access token in the backend meta data. if it's not there, then issue a request for a token. //
|
||||||
// this is not generally meant to be put in the meta data by the app programmer - rather, we're just using //
|
// this is not generally meant to be put in the meta data by the app programmer - rather, we're just using //
|
||||||
// it as a "cheap & easy" way to "cache" the token within our process's memory... //
|
// it as a "cheap & easy" way to "cache" the token within our process's memory... //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
String accessToken = ValueUtils.getValueAsString(backendMetaData.getCustomValue("accessToken"));
|
String accessToken = ValueUtils.getValueAsString(backendMetaData.getCustomValue(accessTokenKey));
|
||||||
Boolean setCredentialsInHeader = BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(backendMetaData.getCustomValue("setCredentialsInHeader")));
|
|
||||||
|
|
||||||
if(!StringUtils.hasContent(accessToken))
|
if(!StringUtils.hasContent(accessToken))
|
||||||
{
|
{
|
||||||
String fullURL = backendMetaData.getBaseUrl() + "oauth/token";
|
|
||||||
String postBody = "grant_type=client_credentials";
|
|
||||||
|
|
||||||
if(!setCredentialsInHeader)
|
|
||||||
{
|
|
||||||
postBody += "&client_id=" + backendMetaData.getClientId() + "&client_secret=" + backendMetaData.getClientSecret();
|
|
||||||
}
|
|
||||||
|
|
||||||
try(CloseableHttpClient client = HttpClients.custom().setConnectionManager(new PoolingHttpClientConnectionManager()).build())
|
try(CloseableHttpClient client = HttpClients.custom().setConnectionManager(new PoolingHttpClientConnectionManager()).build())
|
||||||
{
|
{
|
||||||
HttpPost request = new HttpPost(fullURL);
|
HttpRequestBase request = createOAuth2TokenRequest();
|
||||||
request.setEntity(new StringEntity(postBody, getCharsetForEntity()));
|
|
||||||
|
|
||||||
if(setCredentialsInHeader)
|
|
||||||
{
|
|
||||||
request.setHeader("Authorization", getBasicAuthenticationHeader(backendMetaData.getClientId(), backendMetaData.getClientSecret()));
|
|
||||||
}
|
|
||||||
request.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
|
|
||||||
|
|
||||||
HttpResponse response = executeOAuthTokenRequest(client, request);
|
HttpResponse response = executeOAuthTokenRequest(client, request);
|
||||||
int statusCode = response.getStatusLine().getStatusCode();
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
@ -808,7 +871,7 @@ public class BaseAPIActionUtil
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// stash the access token in the backendMetaData, from which it will be used for future requests //
|
// stash the access token in the backendMetaData, from which it will be used for future requests //
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
backendMetaData.withCustomValue("accessToken", accessToken);
|
backendMetaData.withCustomValue(accessTokenKey, accessToken);
|
||||||
}
|
}
|
||||||
catch(OAuthCredentialsException oce)
|
catch(OAuthCredentialsException oce)
|
||||||
{
|
{
|
||||||
@ -827,6 +890,53 @@ public class BaseAPIActionUtil
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
** For doing OAuth2 authentication, create a request for a token.
|
||||||
|
***************************************************************************/
|
||||||
|
protected HttpRequestBase createOAuth2TokenRequest() throws QException
|
||||||
|
{
|
||||||
|
String fullURL = backendMetaData.getBaseUrl() + "oauth/token";
|
||||||
|
String postBody = "grant_type=client_credentials";
|
||||||
|
|
||||||
|
Pair<String, String> clientIdAndSecret = getClientIdAndSecret();
|
||||||
|
String clientId = clientIdAndSecret.getA();
|
||||||
|
String clientSecret = clientIdAndSecret.getB();
|
||||||
|
|
||||||
|
Boolean setCredentialsInHeader = BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(backendMetaData.getCustomValue("setCredentialsInHeader")));
|
||||||
|
if(!setCredentialsInHeader)
|
||||||
|
{
|
||||||
|
postBody += "&client_id=" + clientId + "&client_secret=" + clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpPost request = new HttpPost(fullURL);
|
||||||
|
request.setEntity(new StringEntity(postBody, getCharsetForEntity()));
|
||||||
|
|
||||||
|
if(setCredentialsInHeader)
|
||||||
|
{
|
||||||
|
request.setHeader("Authorization", getBasicAuthenticationHeader(clientId, clientSecret));
|
||||||
|
}
|
||||||
|
request.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
protected Pair<String, String> getClientIdAndSecret() throws QException
|
||||||
|
{
|
||||||
|
if(backendMetaData.getUsesVariants())
|
||||||
|
{
|
||||||
|
QRecord record = getVariantRecord();
|
||||||
|
return (Pair.of(record.getValueString(backendMetaData.getVariantOptionsTableClientIdField()), record.getValueString(backendMetaData.getVariantOptionsTableClientSecretField())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Pair.of(backendMetaData.getClientId(), backendMetaData.getClientSecret()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Let a subclass change what charset to use for entities (bodies) being posted/put/etc.
|
** Let a subclass change what charset to use for entities (bodies) being posted/put/etc.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -840,6 +950,18 @@ public class BaseAPIActionUtil
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** one-line method, factored out so mock/tests can override
|
** one-line method, factored out so mock/tests can override
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
protected CloseableHttpResponse executeOAuthTokenRequest(CloseableHttpClient client, HttpRequestBase request) throws IOException
|
||||||
|
{
|
||||||
|
return client.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** one-line method, factored out so mock/tests can override
|
||||||
|
** Deprecated, in favor of more generic overload that takes HttpRequestBase
|
||||||
|
*******************************************************************************/
|
||||||
|
@Deprecated
|
||||||
protected CloseableHttpResponse executeOAuthTokenRequest(CloseableHttpClient client, HttpPost request) throws IOException
|
protected CloseableHttpResponse executeOAuthTokenRequest(CloseableHttpClient client, HttpPost request) throws IOException
|
||||||
{
|
{
|
||||||
return client.execute(request);
|
return client.execute(request);
|
||||||
|
@ -28,7 +28,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|||||||
import com.kingsrook.qqq.backend.module.api.actions.BaseAPIActionUtil;
|
import com.kingsrook.qqq.backend.module.api.actions.BaseAPIActionUtil;
|
||||||
import com.kingsrook.qqq.backend.module.api.actions.QHttpResponse;
|
import com.kingsrook.qqq.backend.module.api.actions.QHttpResponse;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.client.methods.HttpRequestBase;
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ public class MockApiActionUtils extends BaseAPIActionUtil
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
protected CloseableHttpResponse executeOAuthTokenRequest(CloseableHttpClient client, HttpPost request) throws IOException
|
protected CloseableHttpResponse executeOAuthTokenRequest(CloseableHttpClient client, HttpRequestBase request) throws IOException
|
||||||
{
|
{
|
||||||
runMockAsserter(request);
|
runMockAsserter(request);
|
||||||
return new MockHttpResponse(mockApiUtilsHelper);
|
return new MockHttpResponse(mockApiUtilsHelper);
|
||||||
|
@ -245,64 +245,31 @@ public abstract class AbstractRDBMSAction
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
protected String makeFromClause(QInstance instance, String tableName, JoinsContext joinsContext, List<Serializable> params)
|
protected String makeFromClause(QInstance instance, String tableName, JoinsContext joinsContext, List<Serializable> params)
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
// start with the main table - un-aliased (well, aliased as itself) //
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
StringBuilder rs = new StringBuilder(escapeIdentifier(getTableName(instance.getTable(tableName))) + " AS " + escapeIdentifier(tableName));
|
StringBuilder rs = new StringBuilder(escapeIdentifier(getTableName(instance.getTable(tableName))) + " AS " + escapeIdentifier(tableName));
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// sort the query joins from the main table "outward"... //
|
|
||||||
// this might not be perfect, e.g., for cases where what we actually might need is a tree of joins... //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
List<QueryJoin> queryJoins = sortQueryJoinsForFromClause(tableName, joinsContext.getQueryJoins());
|
List<QueryJoin> queryJoins = sortQueryJoinsForFromClause(tableName, joinsContext.getQueryJoins());
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
// iterate over joins, adding to the from clause (rs) //
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
for(QueryJoin queryJoin : queryJoins)
|
for(QueryJoin queryJoin : queryJoins)
|
||||||
{
|
{
|
||||||
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
|
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
|
||||||
String joinTableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
String tableNameOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
// add the `<type> JOIN table AS alias` bit to the rs //
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
rs.append(" ").append(queryJoin.getType()).append(" JOIN ")
|
rs.append(" ").append(queryJoin.getType()).append(" JOIN ")
|
||||||
.append(escapeIdentifier(getTableName(joinTable)))
|
.append(escapeIdentifier(getTableName(joinTable)))
|
||||||
.append(" AS ").append(escapeIdentifier(joinTableNameOrAlias));
|
.append(" AS ").append(escapeIdentifier(tableNameOrAlias));
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// find the join in the instance, for building the ON clause //
|
// find the join in the instance, to set the 'on' clause //
|
||||||
// append each sub-clause (condition) into a list, for later joining with AND //
|
////////////////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
List<String> joinClauseList = new ArrayList<>();
|
List<String> joinClauseList = new ArrayList<>();
|
||||||
String baseTableName = Objects.requireNonNullElse(joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getBaseTableOrAlias()), tableName);
|
String baseTableName = Objects.requireNonNullElse(joinsContext.resolveTableNameOrAliasToTableName(queryJoin.getBaseTableOrAlias()), tableName);
|
||||||
QJoinMetaData joinMetaData = Objects.requireNonNull(queryJoin.getJoinMetaData(), () -> "Could not find a join between tables [" + baseTableName + "][" + queryJoin.getJoinTable() + "]");
|
QJoinMetaData joinMetaData = Objects.requireNonNull(queryJoin.getJoinMetaData(), () -> "Could not find a join between tables [" + baseTableName + "][" + queryJoin.getJoinTable() + "]");
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
|
||||||
// loop over join-ons (e.g., multi-column join) //
|
|
||||||
//////////////////////////////////////////////////
|
|
||||||
for(JoinOn joinOn : joinMetaData.getJoinOns())
|
for(JoinOn joinOn : joinMetaData.getJoinOns())
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// figure out if the join needs flipped. We want its left table to equal the queryJoin's base table. //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
QTableMetaData leftTable = instance.getTable(joinMetaData.getLeftTable());
|
QTableMetaData leftTable = instance.getTable(joinMetaData.getLeftTable());
|
||||||
QTableMetaData rightTable = instance.getTable(joinMetaData.getRightTable());
|
QTableMetaData rightTable = instance.getTable(joinMetaData.getRightTable());
|
||||||
|
|
||||||
if(!joinMetaData.getLeftTable().equals(baseTableName))
|
|
||||||
{
|
|
||||||
joinOn = joinOn.flip();
|
|
||||||
QTableMetaData tmpTable = leftTable;
|
|
||||||
leftTable = rightTable;
|
|
||||||
rightTable = tmpTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
// get the table-names-or-aliases to use in the ON clause //
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
String baseTableOrAlias = queryJoin.getBaseTableOrAlias();
|
String baseTableOrAlias = queryJoin.getBaseTableOrAlias();
|
||||||
String joinTableOrAlias = queryJoin.getJoinTableOrItsAlias();
|
|
||||||
if(baseTableOrAlias == null)
|
if(baseTableOrAlias == null)
|
||||||
{
|
{
|
||||||
baseTableOrAlias = leftTable.getName();
|
baseTableOrAlias = leftTable.getName();
|
||||||
@ -312,6 +279,15 @@ public abstract class AbstractRDBMSAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String joinTableOrAlias = queryJoin.getJoinTableOrItsAlias();
|
||||||
|
if(!joinMetaData.getLeftTable().equals(baseTableName))
|
||||||
|
{
|
||||||
|
joinOn = joinOn.flip();
|
||||||
|
QTableMetaData tmpTable = leftTable;
|
||||||
|
leftTable = rightTable;
|
||||||
|
rightTable = tmpTable;
|
||||||
|
}
|
||||||
|
|
||||||
joinClauseList.add(escapeIdentifier(baseTableOrAlias)
|
joinClauseList.add(escapeIdentifier(baseTableOrAlias)
|
||||||
+ "." + escapeIdentifier(getColumnName(leftTable.getField(joinOn.getLeftField())))
|
+ "." + escapeIdentifier(getColumnName(leftTable.getField(joinOn.getLeftField())))
|
||||||
+ " = " + escapeIdentifier(joinTableOrAlias)
|
+ " = " + escapeIdentifier(joinTableOrAlias)
|
||||||
@ -962,7 +938,6 @@ public abstract class AbstractRDBMSAction
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
params = Objects.requireNonNullElse(params, Collections.emptyList());
|
|
||||||
params = params.size() <= 100 ? params : params.subList(0, 99);
|
params = params.size() <= 100 ? params : params.subList(0, 99);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -116,7 +116,7 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
actionTimeoutHelper = new ActionTimeoutHelper(aggregateInput.getTimeoutSeconds(), TimeUnit.SECONDS, new StatementTimeoutCanceller(statement, sql));
|
actionTimeoutHelper = new ActionTimeoutHelper(aggregateInput.getTimeoutSeconds(), TimeUnit.SECONDS, new StatementTimeoutCanceller(statement, sql));
|
||||||
actionTimeoutHelper.start();
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
QueryManager.executeStatement(statement, sql, ((ResultSet resultSet) ->
|
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
// once we've started getting results, go ahead and cancel the timeout //
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
@ -168,10 +168,8 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
|
|
||||||
}), params);
|
}), params);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
logSQL(sql, params, mark);
|
||||||
logSQL(sql, params, mark);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs;
|
return rs;
|
||||||
}
|
}
|
||||||
|
@ -84,10 +84,10 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
|||||||
setSqlAndJoinsInQueryStat(sql, joinsContext);
|
setSqlAndJoinsInQueryStat(sql, joinsContext);
|
||||||
|
|
||||||
CountOutput rs = new CountOutput();
|
CountOutput rs = new CountOutput();
|
||||||
long mark = System.currentTimeMillis();
|
|
||||||
|
|
||||||
try(Connection connection = getConnection(countInput))
|
try(Connection connection = getConnection(countInput))
|
||||||
{
|
{
|
||||||
|
long mark = System.currentTimeMillis();
|
||||||
|
|
||||||
statement = connection.prepareStatement(sql);
|
statement = connection.prepareStatement(sql);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -96,7 +96,7 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
|||||||
actionTimeoutHelper = new ActionTimeoutHelper(countInput.getTimeoutSeconds(), TimeUnit.SECONDS, new StatementTimeoutCanceller(statement, sql));
|
actionTimeoutHelper = new ActionTimeoutHelper(countInput.getTimeoutSeconds(), TimeUnit.SECONDS, new StatementTimeoutCanceller(statement, sql));
|
||||||
actionTimeoutHelper.start();
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
QueryManager.executeStatement(statement, sql, ((ResultSet resultSet) ->
|
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
// once we've started getting results, go ahead and cancel the timeout //
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
@ -116,9 +116,7 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
|||||||
setQueryStatFirstResultTime();
|
setQueryStatFirstResultTime();
|
||||||
|
|
||||||
}), params);
|
}), params);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
logSQL(sql, params, mark);
|
logSQL(sql, params, mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,16 +212,13 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
|
|||||||
// LOG.debug("rowCount 0 trying to delete [" + tableName + "][" + primaryKey + "]");
|
// LOG.debug("rowCount 0 trying to delete [" + tableName + "][" + primaryKey + "]");
|
||||||
// deleteOutput.addRecordWithError(new QRecord(table, primaryKey).withError("Record was not deleted (but no error was given from the database)"));
|
// deleteOutput.addRecordWithError(new QRecord(table, primaryKey).withError("Record was not deleted (but no error was given from the database)"));
|
||||||
// }
|
// }
|
||||||
|
logSQL(sql, List.of(primaryKey), mark);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
LOG.debug("Exception trying to delete [" + tableName + "][" + primaryKey + "]", e);
|
LOG.debug("Exception trying to delete [" + tableName + "][" + primaryKey + "]", e);
|
||||||
deleteOutput.addRecordWithError(new QRecord(table, primaryKey).withError(new SystemErrorStatusMessage("Record was not deleted: " + e.getMessage())));
|
deleteOutput.addRecordWithError(new QRecord(table, primaryKey).withError(new SystemErrorStatusMessage("Record was not deleted: " + e.getMessage())));
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
logSQL(sql, List.of(primaryKey), mark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -231,14 +228,13 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void doDeleteList(Connection connection, QTableMetaData table, List<Serializable> primaryKeys, DeleteOutput deleteOutput) throws QException
|
public void doDeleteList(Connection connection, QTableMetaData table, List<Serializable> primaryKeys, DeleteOutput deleteOutput) throws QException
|
||||||
{
|
{
|
||||||
long mark = System.currentTimeMillis();
|
|
||||||
String sql = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
long mark = System.currentTimeMillis();
|
||||||
|
|
||||||
String tableName = getTableName(table);
|
String tableName = getTableName(table);
|
||||||
String primaryKeyName = getColumnName(table.getField(table.getPrimaryKeyField()));
|
String primaryKeyName = getColumnName(table.getField(table.getPrimaryKeyField()));
|
||||||
sql = "DELETE FROM "
|
String sql = "DELETE FROM "
|
||||||
+ escapeIdentifier(tableName)
|
+ escapeIdentifier(tableName)
|
||||||
+ " WHERE "
|
+ " WHERE "
|
||||||
+ escapeIdentifier(primaryKeyName)
|
+ escapeIdentifier(primaryKeyName)
|
||||||
@ -250,15 +246,13 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
|
|||||||
|
|
||||||
Integer rowCount = QueryManager.executeUpdateForRowCount(connection, sql, primaryKeys);
|
Integer rowCount = QueryManager.executeUpdateForRowCount(connection, sql, primaryKeys);
|
||||||
deleteOutput.addToDeletedRecordCount(rowCount);
|
deleteOutput.addToDeletedRecordCount(rowCount);
|
||||||
|
|
||||||
|
logSQL(sql, primaryKeys, mark);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
throw new QException("Error executing delete: " + e.getMessage(), e);
|
throw new QException("Error executing delete: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
logSQL(sql, primaryKeys, mark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -288,14 +282,12 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte
|
|||||||
{
|
{
|
||||||
int rowCount = QueryManager.executeUpdateForRowCount(connection, sql, params);
|
int rowCount = QueryManager.executeUpdateForRowCount(connection, sql, params);
|
||||||
deleteOutput.setDeletedRecordCount(rowCount);
|
deleteOutput.setDeletedRecordCount(rowCount);
|
||||||
|
|
||||||
|
logSQL(sql, params, mark);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
throw new QException("Error executing delete with filter: " + e.getMessage(), e);
|
throw new QException("Error executing delete with filter: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
logSQL(sql, params, mark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,13 +57,9 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
|
|||||||
InsertOutput rs = new InsertOutput();
|
InsertOutput rs = new InsertOutput();
|
||||||
QTableMetaData table = insertInput.getTable();
|
QTableMetaData table = insertInput.getTable();
|
||||||
|
|
||||||
Connection connection = null;
|
Connection connection = null;
|
||||||
boolean needToCloseConnection = false;
|
boolean needToCloseConnection = false;
|
||||||
|
|
||||||
StringBuilder sql = null;
|
|
||||||
List<Object> params = null;
|
|
||||||
Long mark = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<QFieldMetaData> insertableFields = table.getFields().values().stream()
|
List<QFieldMetaData> insertableFields = table.getFields().values().stream()
|
||||||
@ -92,10 +88,10 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
|
|||||||
|
|
||||||
for(List<QRecord> page : CollectionUtils.getPages(insertInput.getRecords(), QueryManager.PAGE_SIZE))
|
for(List<QRecord> page : CollectionUtils.getPages(insertInput.getRecords(), QueryManager.PAGE_SIZE))
|
||||||
{
|
{
|
||||||
String tableName = escapeIdentifier(getTableName(table));
|
String tableName = escapeIdentifier(getTableName(table));
|
||||||
sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES");
|
StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES");
|
||||||
params = new ArrayList<>();
|
List<Object> params = new ArrayList<>();
|
||||||
int recordIndex = 0;
|
int recordIndex = 0;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
// for each record in the page: //
|
// for each record in the page: //
|
||||||
@ -137,7 +133,7 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
mark = System.currentTimeMillis();
|
Long mark = System.currentTimeMillis();
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
// execute the insert, then foreach record in the input, //
|
// execute the insert, then foreach record in the input, //
|
||||||
@ -167,7 +163,6 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
logSQL(sql, params, mark);
|
|
||||||
throw new QException("Error executing insert: " + e.getMessage(), e);
|
throw new QException("Error executing insert: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
@ -170,7 +170,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||||
|
|
||||||
QueryManager.executeStatement(statement, sql, ((ResultSet resultSet) ->
|
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
// once we've started getting results, go ahead and cancel the timeout //
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
@ -223,12 +223,17 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
|
|
||||||
}), params);
|
}), params);
|
||||||
|
|
||||||
|
logSQL(sql, params, mark);
|
||||||
|
|
||||||
return queryOutput;
|
return queryOutput;
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
logSQL(sql, params, mark);
|
||||||
|
throw (e);
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
logSQL(sql, params, mark);
|
|
||||||
|
|
||||||
if(actionTimeoutHelper != null)
|
if(actionTimeoutHelper != null)
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
@ -361,7 +366,10 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
{
|
{
|
||||||
RDBMSBackendMetaData rdbmsBackendMetaData = (RDBMSBackendMetaData) queryInput.getBackend();
|
RDBMSBackendMetaData rdbmsBackendMetaData = (RDBMSBackendMetaData) queryInput.getBackend();
|
||||||
|
|
||||||
if(RDBMSBackendMetaData.VENDOR_MYSQL.equals(rdbmsBackendMetaData.getVendor()) || RDBMSBackendMetaData.VENDOR_AURORA_MYSQL.equals(rdbmsBackendMetaData.getVendor()))
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - remove "aurora" - it's a legacy value here for a staged rollout //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(RDBMSBackendMetaData.VENDOR_MYSQL.equals(rdbmsBackendMetaData.getVendor()) || RDBMSBackendMetaData.VENDOR_AURORA_MYSQL.equals(rdbmsBackendMetaData.getVendor()) || "aurora".equals(rdbmsBackendMetaData.getVendor()))
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// mysql "optimization", presumably here - from Result Set section of //
|
// mysql "optimization", presumably here - from Result Set section of //
|
||||||
|
@ -179,15 +179,10 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// let query manager do the batch updates - note that it will internally page //
|
// let query manager do the batch updates - note that it will internally page //
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
try
|
QueryManager.executeBatchUpdate(connection, sql, values);
|
||||||
{
|
incrementStatus(updateInput, recordList.size());
|
||||||
QueryManager.executeBatchUpdate(connection, sql, values);
|
|
||||||
incrementStatus(updateInput, recordList.size());
|
logSQL(sql, values, mark);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
logSQL(sql, values, mark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -254,15 +249,10 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
|
|||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
// let query manager do the update //
|
// let query manager do the update //
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
try
|
QueryManager.executeUpdate(connection, sql, params);
|
||||||
{
|
incrementStatus(updateInput, page.size());
|
||||||
QueryManager.executeUpdate(connection, sql, params);
|
|
||||||
incrementStatus(updateInput, page.size());
|
logSQL(sql, params, mark);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
logSQL(sql, params, mark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,10 @@ public class ConnectionManager
|
|||||||
|
|
||||||
return switch(backend.getVendor())
|
return switch(backend.getVendor())
|
||||||
{
|
{
|
||||||
case RDBMSBackendMetaData.VENDOR_MYSQL, RDBMSBackendMetaData.VENDOR_AURORA_MYSQL -> "com.mysql.cj.jdbc.Driver";
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - remove "aurora" - it's a legacy value here for a staged rollout //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
case RDBMSBackendMetaData.VENDOR_MYSQL, RDBMSBackendMetaData.VENDOR_AURORA_MYSQL, "aurora" -> "com.mysql.cj.jdbc.Driver";
|
||||||
case RDBMSBackendMetaData.VENDOR_H2 -> "org.h2.Driver";
|
case RDBMSBackendMetaData.VENDOR_H2 -> "org.h2.Driver";
|
||||||
default -> throw (new IllegalStateException("We do not know what jdbc driver to use for vendor name [" + backend.getVendor() + "]. Try setting jdbcDriverClassName in your backend meta data."));
|
default -> throw (new IllegalStateException("We do not know what jdbc driver to use for vendor name [" + backend.getVendor() + "]. Try setting jdbcDriverClassName in your backend meta data."));
|
||||||
};
|
};
|
||||||
@ -175,7 +178,10 @@ public class ConnectionManager
|
|||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// jdbcURL = "jdbc:mysql:aws://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=CONVERT_TO_NULL";
|
// jdbcURL = "jdbc:mysql:aws://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=CONVERT_TO_NULL";
|
||||||
|
|
||||||
case RDBMSBackendMetaData.VENDOR_AURORA_MYSQL -> "jdbc:mysql://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=false";
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - remove "aurora" - it's a legacy value here for a staged rollout //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
case RDBMSBackendMetaData.VENDOR_AURORA_MYSQL, "aurora" -> "jdbc:mysql://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull&useSSL=false";
|
||||||
case RDBMSBackendMetaData.VENDOR_MYSQL -> "jdbc:mysql://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull";
|
case RDBMSBackendMetaData.VENDOR_MYSQL -> "jdbc:mysql://" + backend.getHostName() + ":" + backend.getPort() + "/" + backend.getDatabaseName() + "?rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull";
|
||||||
case RDBMSBackendMetaData.VENDOR_H2 -> "jdbc:h2:" + backend.getHostName() + ":" + backend.getDatabaseName() + ";MODE=MySQL;DB_CLOSE_DELAY=-1";
|
case RDBMSBackendMetaData.VENDOR_H2 -> "jdbc:h2:" + backend.getHostName() + ":" + backend.getDatabaseName() + ";MODE=MySQL;DB_CLOSE_DELAY=-1";
|
||||||
default -> throw new IllegalArgumentException("Unsupported rdbms backend vendor: " + backend.getVendor());
|
default -> throw new IllegalArgumentException("Unsupported rdbms backend vendor: " + backend.getVendor());
|
||||||
|
@ -32,7 +32,6 @@ import java.sql.ResultSet;
|
|||||||
import java.sql.ResultSetMetaData;
|
import java.sql.ResultSetMetaData;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.sql.Time;
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@ -57,7 +56,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValu
|
|||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import org.apache.commons.lang.NotImplementedException;
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -123,17 +121,6 @@ public class QueryManager
|
|||||||
** customized settings/optimizations).
|
** customized settings/optimizations).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void executeStatement(PreparedStatement statement, ResultSetProcessor processor, Object... params) throws SQLException, QException
|
public static void executeStatement(PreparedStatement statement, ResultSetProcessor processor, Object... params) throws SQLException, QException
|
||||||
{
|
|
||||||
executeStatement(statement, null, processor, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Let the caller provide their own prepared statement (e.g., possibly with some
|
|
||||||
** customized settings/optimizations).
|
|
||||||
*******************************************************************************/
|
|
||||||
public static void executeStatement(PreparedStatement statement, CharSequence sql, ResultSetProcessor processor, Object... params) throws SQLException, QException
|
|
||||||
{
|
{
|
||||||
ResultSet resultSet = null;
|
ResultSet resultSet = null;
|
||||||
|
|
||||||
@ -149,14 +136,6 @@ public class QueryManager
|
|||||||
processor.processResultSet(resultSet);
|
processor.processResultSet(resultSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(SQLException e)
|
|
||||||
{
|
|
||||||
if(sql != null)
|
|
||||||
{
|
|
||||||
LOG.warn("SQLException", e, logPair("sql", sql));
|
|
||||||
}
|
|
||||||
throw (e);
|
|
||||||
}
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if(resultSet != null)
|
if(resultSet != null)
|
||||||
@ -393,17 +372,10 @@ public class QueryManager
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static PreparedStatement executeUpdate(Connection connection, String sql, Object... params) throws SQLException
|
public static PreparedStatement executeUpdate(Connection connection, String sql, Object... params) throws SQLException
|
||||||
{
|
{
|
||||||
try(PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params))
|
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
|
||||||
{
|
incrementStatistic(STAT_QUERIES_RAN);
|
||||||
incrementStatistic(STAT_QUERIES_RAN);
|
statement.executeUpdate();
|
||||||
statement.executeUpdate();
|
return (statement);
|
||||||
return (statement);
|
|
||||||
}
|
|
||||||
catch(SQLException e)
|
|
||||||
{
|
|
||||||
LOG.warn("SQLException", e, logPair("sql", sql));
|
|
||||||
throw (e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -413,17 +385,10 @@ public class QueryManager
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static PreparedStatement executeUpdate(Connection connection, String sql, List<Object> params) throws SQLException
|
public static PreparedStatement executeUpdate(Connection connection, String sql, List<Object> params) throws SQLException
|
||||||
{
|
{
|
||||||
try(PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params))
|
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
|
||||||
{
|
incrementStatistic(STAT_QUERIES_RAN);
|
||||||
incrementStatistic(STAT_QUERIES_RAN);
|
statement.executeUpdate();
|
||||||
statement.executeUpdate();
|
return (statement);
|
||||||
return (statement);
|
|
||||||
}
|
|
||||||
catch(SQLException e)
|
|
||||||
{
|
|
||||||
LOG.warn("SQLException", e, logPair("sql", sql));
|
|
||||||
throw (e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -471,11 +436,6 @@ public class QueryManager
|
|||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
return (statement.getUpdateCount());
|
return (statement.getUpdateCount());
|
||||||
}
|
}
|
||||||
catch(SQLException e)
|
|
||||||
{
|
|
||||||
LOG.warn("SQLException", e, logPair("sql", sql));
|
|
||||||
throw (e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -528,24 +488,19 @@ public class QueryManager
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static List<Integer> executeInsertForGeneratedIds(Connection connection, String sql, List<Object> params) throws SQLException
|
public static List<Integer> executeInsertForGeneratedIds(Connection connection, String sql, List<Object> params) throws SQLException
|
||||||
{
|
{
|
||||||
|
List<Integer> rs = new ArrayList<>();
|
||||||
try(PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS))
|
try(PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS))
|
||||||
{
|
{
|
||||||
bindParams(params.toArray(), statement);
|
bindParams(params.toArray(), statement);
|
||||||
incrementStatistic(STAT_QUERIES_RAN);
|
incrementStatistic(STAT_QUERIES_RAN);
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
ResultSet generatedKeys = statement.getGeneratedKeys();
|
ResultSet generatedKeys = statement.getGeneratedKeys();
|
||||||
List<Integer> rs = new ArrayList<>();
|
|
||||||
while(generatedKeys.next())
|
while(generatedKeys.next())
|
||||||
{
|
{
|
||||||
rs.add(getInteger(generatedKeys, 1));
|
rs.add(getInteger(generatedKeys, 1));
|
||||||
}
|
}
|
||||||
return (rs);
|
|
||||||
}
|
|
||||||
catch(SQLException e)
|
|
||||||
{
|
|
||||||
LOG.warn("SQLException", e, logPair("sql", sql));
|
|
||||||
throw (e);
|
|
||||||
}
|
}
|
||||||
|
return (rs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -792,14 +747,14 @@ public class QueryManager
|
|||||||
else if(value instanceof LocalDate ld)
|
else if(value instanceof LocalDate ld)
|
||||||
{
|
{
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
Date date = new Date(ld.getYear() - 1900, ld.getMonthValue() - 1, ld.getDayOfMonth());
|
java.sql.Date date = new java.sql.Date(ld.getYear() - 1900, ld.getMonthValue() - 1, ld.getDayOfMonth());
|
||||||
statement.setDate(index, date);
|
statement.setDate(index, date);
|
||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
else if(value instanceof LocalTime lt)
|
else if(value instanceof LocalTime lt)
|
||||||
{
|
{
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
Time time = new Time(lt.getHour(), lt.getMinute(), lt.getSecond());
|
java.sql.Time time = new java.sql.Time(lt.getHour(), lt.getMinute(), lt.getSecond());
|
||||||
statement.setTime(index, time);
|
statement.setTime(index, time);
|
||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
@ -988,7 +943,7 @@ public class QueryManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
statement.setDate(index, new Date(value.getTime()));
|
statement.setDate(index, new java.sql.Date(value.getTime()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +273,6 @@ public class TestUtils
|
|||||||
.withJoinNameChain(List.of("orderInstructionsJoinOrder")))
|
.withJoinNameChain(List.of("orderInstructionsJoinOrder")))
|
||||||
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
|
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
|
||||||
.withField(new QFieldMetaData("instructions", QFieldType.STRING))
|
.withField(new QFieldMetaData("instructions", QFieldType.STRING))
|
||||||
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ORDER).withJoinPath(List.of("orderInstructionsJoinOrder")))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
|
||||||
@ -396,10 +395,10 @@ public class TestUtils
|
|||||||
|
|
||||||
qInstance.addJoin(new QJoinMetaData()
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
.withName("orderInstructionsJoinOrder")
|
.withName("orderInstructionsJoinOrder")
|
||||||
.withRightTable(TABLE_NAME_ORDER_INSTRUCTIONS)
|
.withLeftTable(TABLE_NAME_ORDER_INSTRUCTIONS)
|
||||||
.withLeftTable(TABLE_NAME_ORDER)
|
.withRightTable(TABLE_NAME_ORDER)
|
||||||
.withType(JoinType.MANY_TO_ONE)
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
.withJoinOn(new JoinOn("id", "orderId"))
|
.withJoinOn(new JoinOn("orderId", "id"))
|
||||||
);
|
);
|
||||||
|
|
||||||
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||||
|
@ -54,7 +54,6 @@ 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.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;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -70,6 +69,10 @@ public class RDBMSQueryActionJoinsTest extends RDBMSActionTest
|
|||||||
public void beforeEach() throws Exception
|
public void beforeEach() throws Exception
|
||||||
{
|
{
|
||||||
super.primeTestDatabase();
|
super.primeTestDatabase();
|
||||||
|
|
||||||
|
AbstractRDBMSAction.setLogSQL(true);
|
||||||
|
AbstractRDBMSAction.setLogSQLReformat(true);
|
||||||
|
AbstractRDBMSAction.setLogSQLOutput("system.out");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -906,7 +909,7 @@ public class RDBMSQueryActionJoinsTest extends RDBMSActionTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Test
|
@Test
|
||||||
void testMultipleReversedDirectionJoinsBetweenSameTablesAllAccessKey() throws QException
|
void testMultipleReversedDirectionJoinsBetweenSameTables() throws QException
|
||||||
{
|
{
|
||||||
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));
|
||||||
|
|
||||||
@ -989,32 +992,6 @@ public class RDBMSQueryActionJoinsTest extends RDBMSActionTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** We had, at one time, a bug where, for tables with 2 joins between each other,
|
|
||||||
** an ON clause could get written using the wrong table name in one part.
|
|
||||||
**
|
|
||||||
** With that bug, this QueryAction.execute would throw an SQL Exception.
|
|
||||||
**
|
|
||||||
** So this test, just makes sure that no such exception gets thrown.
|
|
||||||
*******************************************************************************/
|
|
||||||
@Test
|
|
||||||
void testFlippedJoinForOnClause() throws QException
|
|
||||||
{
|
|
||||||
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1));
|
|
||||||
|
|
||||||
QueryInput queryInput = new QueryInput();
|
|
||||||
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER_INSTRUCTIONS);
|
|
||||||
queryInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_ORDER));
|
|
||||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
|
||||||
assertFalse(queryOutput.getRecords().isEmpty());
|
|
||||||
|
|
||||||
////////////////////////////////////
|
|
||||||
// if no exception, then we pass. //
|
|
||||||
////////////////////////////////////
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Addressing a regression where a table was brought into a query for its
|
** Addressing a regression where a table was brought into a query for its
|
||||||
** security field, but it was a write-scope lock, so, it shouldn't have been.
|
** security field, but it was a write-scope lock, so, it shouldn't have been.
|
||||||
|
Reference in New Issue
Block a user