Compare commits

..

6 Commits

38 changed files with 413 additions and 2225 deletions

View File

@ -1,86 +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.dashboard.widgets;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.AlertData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
/*******************************************************************************
** Widget that can add an Alert to a process screen.
**
** In the process, you'll want values:
** - alertType - name of entry in AlertType enum (ERROR, WARNING, SUCCESS)
** - alertHtml - html to display inside the alert (other than its icon)
*******************************************************************************/
public class ProcessAlertWidget extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData>
{
public static final String NAME = "ProcessAlertWidget";
/*******************************************************************************
**
*******************************************************************************/
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
AlertData.AlertType alertType = AlertData.AlertType.WARNING;
if(input.getQueryParams().containsKey("alertType"))
{
alertType = AlertData.AlertType.valueOf(input.getQueryParams().get("alertType"));
}
String html = "Warning";
if(input.getQueryParams().containsKey("alertHtml"))
{
html = input.getQueryParams().get("alertHtml");
}
return (new RenderWidgetOutput(new AlertData(alertType, html)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QWidgetMetaData produce(QInstance qInstance) throws QException
{
return new QWidgetMetaData()
.withType(WidgetType.ALERT.getType())
.withGridColumns(12)
.withName(NAME)
.withIsCard(false)
.withShowReloadButton(false)
.withCodeReference(new QCodeReference(getClass()));
}
}

View File

@ -44,7 +44,6 @@ 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.ExportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput; import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -217,8 +216,7 @@ public class ExportAction
} }
queryInput.getFilter().setLimit(exportInput.getLimit()); queryInput.getFilter().setLimit(exportInput.getLimit());
queryInput.setShouldTranslatePossibleValues(true); queryInput.setShouldTranslatePossibleValues(true);
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS); queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// tell this query that it needs to put its output into a pipe // // tell this query that it needs to put its output into a pipe //

View File

@ -59,7 +59,6 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext; import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
@ -418,8 +417,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
queryInput.setTableName(dataSource.getSourceTable()); queryInput.setTableName(dataSource.getSourceTable());
queryInput.setFilter(queryFilter); queryInput.setFilter(queryFilter);
queryInput.setQueryJoins(dataSource.getQueryJoins()); queryInput.setQueryJoins(dataSource.getQueryJoins());
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS); queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
queryInput.setShouldTranslatePossibleValues(true); queryInput.setShouldTranslatePossibleValues(true);
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter()))); queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter())));

View File

@ -32,12 +32,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -77,6 +78,45 @@ public class QPossibleValueTranslator
private int maxSizePerPvsCache = 50_000; private int maxSizePerPvsCache = 50_000;
private Map<String, QBackendTransaction> transactionsPerTable = new HashMap<>();
// todo not commit - remove instance & session - use Context
boolean useTransactionsAsConnectionPool = false;
/*******************************************************************************
**
*******************************************************************************/
private QBackendTransaction getTransaction(String tableName)
{
/////////////////////////////////////////////////////////////
// mmm, this does cut down on connections used - //
// especially seems helpful in big exports. //
// but, let's just start using connection pools instead... //
/////////////////////////////////////////////////////////////
if(useTransactionsAsConnectionPool)
{
try
{
if(!transactionsPerTable.containsKey(tableName))
{
transactionsPerTable.put(tableName, QBackendTransaction.openFor(new InsertInput(tableName)));
}
return (transactionsPerTable.get(tableName));
}
catch(Exception e)
{
LOG.warn("Error opening transaction for table", logPair("tableName", tableName));
}
}
return null;
}
/******************************************************************************* /*******************************************************************************
@ -561,7 +601,7 @@ public class QPossibleValueTranslator
QueryInput queryInput = new QueryInput(); QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName); queryInput.setTableName(tableName);
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(idField, QCriteriaOperator.IN, page))); queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(idField, QCriteriaOperator.IN, page)));
queryInput.hasQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND); queryInput.setTransaction(getTransaction(tableName));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// when querying for possible values, we do want to generate their display values, which makes record labels, which are usually used as PVS labels // // when querying for possible values, we do want to generate their display values, which makes record labels, which are usually used as PVS labels //

View File

@ -384,9 +384,9 @@ public class QInstanceEnricher
process.setLabel(nameToLabel(process.getName())); process.setLabel(nameToLabel(process.getName()));
} }
for(QStepMetaData step : CollectionUtils.nonNullMap(process.getAllSteps()).values()) if(process.getStepList() != null)
{ {
enrichStep(step); process.getStepList().forEach(this::enrichStep);
} }
for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values()) for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values())

View File

@ -1,38 +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.model.actions.tables;
/*******************************************************************************
** Information about the query that an application (or qqq service) may know and
** want to tell the backend, that can help influence how the backend processes
** query.
**
** For example, a query with potentially a large result set, for MySQL backend,
** we may want to configure the result set to stream results rather than do its
** default in-memory thing. See RDBMSQueryAction for usage.
*******************************************************************************/
public enum QueryHint
{
POTENTIALLY_LARGE_NUMBER_OF_RESULTS,
MAY_USE_READ_ONLY_BACKEND
}

View File

@ -23,10 +23,8 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.aggregate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
@ -46,8 +44,6 @@ public class AggregateInput extends AbstractTableActionInput
private List<QueryJoin> queryJoins = null; private List<QueryJoin> queryJoins = null;
private EnumSet<QueryHint> queryHints = EnumSet.noneOf(QueryHint.class);
/******************************************************************************* /*******************************************************************************
@ -306,78 +302,4 @@ public class AggregateInput extends AbstractTableActionInput
return (this); return (this);
} }
/*******************************************************************************
** Getter for queryHints
*******************************************************************************/
public EnumSet<QueryHint> getQueryHints()
{
return (this.queryHints);
}
/*******************************************************************************
** Setter for queryHints
*******************************************************************************/
public void setQueryHints(EnumSet<QueryHint> queryHints)
{
this.queryHints = queryHints;
}
/*******************************************************************************
** Fluent setter for queryHints
*******************************************************************************/
public AggregateInput withQueryHints(EnumSet<QueryHint> queryHints)
{
this.queryHints = queryHints;
return (this);
}
/*******************************************************************************
** Fluent setter for queryHints
*******************************************************************************/
public AggregateInput withQueryHint(QueryHint queryHint)
{
if(this.queryHints == null)
{
this.queryHints = EnumSet.noneOf(QueryHint.class);
}
this.queryHints.add(queryHint);
return (this);
}
/*******************************************************************************
** Fluent setter for queryHints
*******************************************************************************/
public AggregateInput withoutQueryHint(QueryHint queryHint)
{
if(this.queryHints != null)
{
this.queryHints.remove(queryHint);
}
return (this);
}
/*******************************************************************************
** null-safely check if query hints map contains the specified hint
*******************************************************************************/
public boolean hasQueryHint(QueryHint queryHint)
{
if(this.queryHints == null)
{
return (false);
}
return (queryHints.contains(queryHint));
}
} }

View File

@ -23,10 +23,8 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.count;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
@ -44,8 +42,6 @@ public class CountInput extends AbstractTableActionInput
private List<QueryJoin> queryJoins = null; private List<QueryJoin> queryJoins = null;
private Boolean includeDistinctCount = false; private Boolean includeDistinctCount = false;
private EnumSet<QueryHint> queryHints = EnumSet.noneOf(QueryHint.class);
/******************************************************************************* /*******************************************************************************
@ -211,78 +207,4 @@ public class CountInput extends AbstractTableActionInput
return (this); return (this);
} }
/*******************************************************************************
** Getter for queryHints
*******************************************************************************/
public EnumSet<QueryHint> getQueryHints()
{
return (this.queryHints);
}
/*******************************************************************************
** Setter for queryHints
*******************************************************************************/
public void setQueryHints(EnumSet<QueryHint> queryHints)
{
this.queryHints = queryHints;
}
/*******************************************************************************
** Fluent setter for queryHints
*******************************************************************************/
public CountInput withQueryHints(EnumSet<QueryHint> queryHints)
{
this.queryHints = queryHints;
return (this);
}
/*******************************************************************************
** Fluent setter for queryHints
*******************************************************************************/
public CountInput withQueryHint(QueryHint queryHint)
{
if(this.queryHints == null)
{
this.queryHints = EnumSet.noneOf(QueryHint.class);
}
this.queryHints.add(queryHint);
return (this);
}
/*******************************************************************************
** Fluent setter for queryHints
*******************************************************************************/
public CountInput withoutQueryHint(QueryHint queryHint)
{
if(this.queryHints != null)
{
this.queryHints.remove(queryHint);
}
return (this);
}
/*******************************************************************************
** null-safely check if query hints map contains the specified hint
*******************************************************************************/
public boolean hasQueryHint(QueryHint queryHint)
{
if(this.queryHints == null)
{
return (false);
}
return (queryHints.contains(queryHint));
}
} }

View File

@ -31,16 +31,12 @@ import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction; import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe; import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface; import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
/******************************************************************************* /*******************************************************************************
** Input data for the Query action ** Input data for the Query action
** **
** Todo - maybe make a class between AbstractTableActionInput and {QueryInput,
** CountInput, and AggregateInput}, with common attributes for all of these
** "read" operations (like, queryHints,
*******************************************************************************/ *******************************************************************************/
public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface, Cloneable public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface, Cloneable
{ {
@ -78,6 +74,22 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
/*******************************************************************************
** Information about the query that an application (or qqq service) may know and
** want to tell the backend, that can help influence how the backend processes
** query.
**
** For example, a query with potentially a large result set, for MySQL backend,
** we may want to configure the result set to stream results rather than do its
** default in-memory thing. See RDBMSQueryAction for usage.
*******************************************************************************/
public enum QueryHint
{
POTENTIALLY_LARGE_NUMBER_OF_RESULTS
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -671,19 +683,4 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
return (this); return (this);
} }
/*******************************************************************************
** null-safely check if query hints map contains the specified hint
*******************************************************************************/
public boolean hasQueryHint(QueryHint queryHint)
{
if(this.queryHints == null)
{
return (false);
}
return (queryHints.contains(queryHint));
}
} }

View File

@ -1,196 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.util.List;
/*******************************************************************************
** Model containing datastructure expected by frontend filter and columns setup widget
**
*******************************************************************************/
public class FilterAndColumnsSetupData extends QWidgetData
{
private String tableName;
private Boolean allowVariables = false;
private Boolean hideColumns = false;
private List<String> filterDefaultFieldNames;
/*******************************************************************************
**
*******************************************************************************/
public FilterAndColumnsSetupData()
{
}
/*******************************************************************************
**
*******************************************************************************/
public FilterAndColumnsSetupData(String tableName, Boolean allowVariables, Boolean hideColumns, List<String> filterDefaultFieldNames)
{
this.tableName = tableName;
this.allowVariables = allowVariables;
this.hideColumns = hideColumns;
this.filterDefaultFieldNames = filterDefaultFieldNames;
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public String getType()
{
return WidgetType.FILTER_AND_COLUMNS_SETUP.getType();
}
/*******************************************************************************
** Getter for tableName
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
*******************************************************************************/
public FilterAndColumnsSetupData withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Getter for hideColumns
*******************************************************************************/
public Boolean getHideColumns()
{
return (this.hideColumns);
}
/*******************************************************************************
** Setter for hideColumns
*******************************************************************************/
public void setHideColumns(Boolean hideColumns)
{
this.hideColumns = hideColumns;
}
/*******************************************************************************
** Fluent setter for hideColumns
*******************************************************************************/
public FilterAndColumnsSetupData withHideColumns(Boolean hideColumns)
{
this.hideColumns = hideColumns;
return (this);
}
/*******************************************************************************
** Getter for filterDefaultFieldNames
*******************************************************************************/
public List<String> getFilterDefaultFieldNames()
{
return (this.filterDefaultFieldNames);
}
/*******************************************************************************
** Setter for filterDefaultFieldNames
*******************************************************************************/
public void setFilterDefaultFieldNames(List<String> filterDefaultFieldNames)
{
this.filterDefaultFieldNames = filterDefaultFieldNames;
}
/*******************************************************************************
** Fluent setter for filterDefaultFieldNames
*******************************************************************************/
public FilterAndColumnsSetupData withFilterDefaultFieldNames(List<String> filterDefaultFieldNames)
{
this.filterDefaultFieldNames = filterDefaultFieldNames;
return (this);
}
/*******************************************************************************
** Getter for allowVariables
*******************************************************************************/
public Boolean getAllowVariables()
{
return (this.allowVariables);
}
/*******************************************************************************
** Setter for allowVariables
*******************************************************************************/
public void setAllowVariables(Boolean allowVariables)
{
this.allowVariables = allowVariables;
}
/*******************************************************************************
** Fluent setter for allowVariables
*******************************************************************************/
public FilterAndColumnsSetupData withAllowVariables(Boolean allowVariables)
{
this.allowVariables = allowVariables;
return (this);
}
}

View File

@ -1,45 +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.model.savedreports;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.FilterAndColumnsSetupData;
/*******************************************************************************
**
*******************************************************************************/
public class SavedReportsFilterAndColumnsSetupRenderer extends AbstractWidgetRenderer
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{
return (new RenderWidgetOutput(new FilterAndColumnsSetupData(null, true, false, null)));
}
}

View File

@ -236,7 +236,7 @@ public class SavedReportsMetaDataProvider
.withLabel("Filters and Columns") .withLabel("Filters and Columns")
.withIsCard(true) .withIsCard(true)
.withType(WidgetType.FILTER_AND_COLUMNS_SETUP.getType()) .withType(WidgetType.FILTER_AND_COLUMNS_SETUP.getType())
.withCodeReference(new QCodeReference(SavedReportsFilterAndColumnsSetupRenderer.class)); .withCodeReference(new QCodeReference(DefaultWidgetRenderer.class));
} }

View File

@ -173,14 +173,6 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
transformStep.postRun(postRunInput, postRunOutput); transformStep.postRun(postRunInput, postRunOutput);
loadStep.postRun(postRunInput, postRunOutput); loadStep.postRun(postRunInput, postRunOutput);
//////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state //
//////////////////////////////////////////////////////////////////////
if(postRunOutput.getUpdatedFrontendStepList() != null)
{
runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList());
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// explicitly copy values back into the runStepOutput from the post-run output // // explicitly copy values back into the runStepOutput from the post-run output //
// this might not be needed, since they (presumably) share a processState object, but just in case that changes... // // this might not be needed, since they (presumably) share a processState object, but just in case that changes... //
@ -278,15 +270,6 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
transformStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput); transformStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput);
List<AuditInput> auditInputListFromTransform = streamedBackendStepOutput.getAuditInputList(); List<AuditInput> auditInputListFromTransform = streamedBackendStepOutput.getAuditInputList();
//////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state //
//////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null)
{
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList());
}
//////////////////////////////////////////////// ////////////////////////////////////////////////
// pass the records through the load function // // pass the records through the load function //
//////////////////////////////////////////////// ////////////////////////////////////////////////
@ -296,15 +279,6 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
loadStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput); loadStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput);
List<AuditInput> auditInputListFromLoad = streamedBackendStepOutput.getAuditInputList(); List<AuditInput> auditInputListFromLoad = streamedBackendStepOutput.getAuditInputList();
//////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state //
//////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null)
{
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList());
}
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
// copy a small number of records to the output list // // copy a small number of records to the output list //
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////

View File

@ -145,9 +145,7 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
BackendStepPostRunInput postRunInput = new BackendStepPostRunInput(runBackendStepInput); BackendStepPostRunInput postRunInput = new BackendStepPostRunInput(runBackendStepInput);
transformStep.postRun(postRunInput, postRunOutput); transformStep.postRun(postRunInput, postRunOutput);
////////////////////////////////////////////////////////////////////// // todo figure out what kind of test we can get on this
// propagate data from inner-step state to process-level step state //
//////////////////////////////////////////////////////////////////////
if(postRunOutput.getUpdatedFrontendStepList() != null) if(postRunOutput.getUpdatedFrontendStepList() != null)
{ {
runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList()); runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList());
@ -216,15 +214,6 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
transformStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput); transformStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput);
//////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state //
//////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null)
{
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList());
}
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
// add the transformed records to the output list // // add the transformed records to the output list //
//////////////////////////////////////////////////// ////////////////////////////////////////////////////

View File

@ -142,9 +142,6 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
BackendStepPostRunInput postRunInput = new BackendStepPostRunInput(runBackendStepInput); BackendStepPostRunInput postRunInput = new BackendStepPostRunInput(runBackendStepInput);
transformStep.postRun(postRunInput, postRunOutput); transformStep.postRun(postRunInput, postRunOutput);
//////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state //
//////////////////////////////////////////////////////////////////////
if(postRunOutput.getUpdatedFrontendStepList() != null) if(postRunOutput.getUpdatedFrontendStepList() != null)
{ {
runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList()); runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList());
@ -180,15 +177,6 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
transformStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput); transformStep.runOnePage(streamedBackendStepInput, streamedBackendStepOutput);
//////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state //
//////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null)
{
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList());
}
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
// copy a small number of records to the output list // // copy a small number of records to the output list //
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.processes.implementations.garbagecollecto
import java.time.Instant; import java.time.Instant;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
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.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep; import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
@ -67,7 +66,7 @@ public class GarbageCollectorExtractStep extends ExtractViaQueryStep
@Override @Override
protected void customizeInputPreQuery(QueryInput queryInput) protected void customizeInputPreQuery(QueryInput queryInput)
{ {
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS); queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
} }
} }

View File

@ -103,13 +103,12 @@ public class ProcessLockUtils
// if inserting failed... see if we can get existing lock // // if inserting failed... see if we can get existing lock //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
StringBuilder existingLockDetails = new StringBuilder(); StringBuilder existingLockDetails = new StringBuilder();
ProcessLock existingLock = null;
if(CollectionUtils.nullSafeHasContents(insertOutputRecord.getErrors())) if(CollectionUtils.nullSafeHasContents(insertOutputRecord.getErrors()))
{ {
QRecord existingLockRecord = new GetAction().executeForRecord(new GetInput(ProcessLock.TABLE_NAME).withUniqueKey(Map.of("key", key, "processLockTypeId", lockType.getId()))); QRecord existingLockRecord = new GetAction().executeForRecord(new GetInput(ProcessLock.TABLE_NAME).withUniqueKey(Map.of("key", key, "processLockTypeId", lockType.getId())));
if(existingLockRecord != null) if(existingLockRecord != null)
{ {
existingLock = new ProcessLock(existingLockRecord); ProcessLock existingLock = new ProcessLock(existingLockRecord);
if(StringUtils.hasContent(existingLock.getUserId())) if(StringUtils.hasContent(existingLock.getUserId()))
{ {
existingLockDetails.append("Held by: ").append(existingLock.getUserId()); existingLockDetails.append("Held by: ").append(existingLock.getUserId());
@ -154,8 +153,7 @@ public class ProcessLockUtils
///////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////
LOG.info("Errors in process lock record after attempted insert", logPair("errors", insertOutputRecord.getErrors()), LOG.info("Errors in process lock record after attempted insert", logPair("errors", insertOutputRecord.getErrors()),
logPair("key", key), logPair("type", typeName), logPair("details", details)); logPair("key", key), logPair("type", typeName), logPair("details", details));
throw (new UnableToObtainProcessLockException("A Process Lock already exists for key [" + key + "] of type [" + typeName + "], " + existingLockDetails) throw (new UnableToObtainProcessLockException("A Process Lock already exists for key [" + key + "] of type [" + typeName + "], " + existingLockDetails));
.withExistingLock(existingLock));
} }
LOG.info("Created process lock", logPair("id", processLock.getId()), LOG.info("Created process lock", logPair("id", processLock.getId()),
@ -204,15 +202,12 @@ public class ProcessLockUtils
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
// this variable can never be null with current code-path, but prefer to be defensive regardless // // var can never be null with current code-path, but prefer defensiveness regardless. //
/////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("ConstantValue") @SuppressWarnings("ConstantValue")
String suffix = lastCaughtUnableToObtainProcessLockException == null ? "" : ": " + lastCaughtUnableToObtainProcessLockException.getMessage(); String suffix = lastCaughtUnableToObtainProcessLockException == null ? "" : ": " + lastCaughtUnableToObtainProcessLockException.getMessage();
throw (new UnableToObtainProcessLockException("Unable to obtain process lock for key [" + key + "] in type [" + type + "] after [" + maxWait + "]" + suffix));
//noinspection ConstantValue
throw (new UnableToObtainProcessLockException("Unable to obtain process lock for key [" + key + "] in type [" + type + "] after [" + maxWait + "]" + suffix)
.withExistingLock(lastCaughtUnableToObtainProcessLockException == null ? null : lastCaughtUnableToObtainProcessLockException.getExistingLock()));
} }

View File

@ -30,9 +30,6 @@ import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
*******************************************************************************/ *******************************************************************************/
public class UnableToObtainProcessLockException extends QUserFacingException public class UnableToObtainProcessLockException extends QUserFacingException
{ {
private ProcessLock existingLock;
/******************************************************************************* /*******************************************************************************
** **
@ -52,35 +49,4 @@ public class UnableToObtainProcessLockException extends QUserFacingException
super(message, cause); super(message, cause);
} }
/*******************************************************************************
** Getter for existingLock
*******************************************************************************/
public ProcessLock getExistingLock()
{
return (this.existingLock);
}
/*******************************************************************************
** Setter for existingLock
*******************************************************************************/
public void setExistingLock(ProcessLock existingLock)
{
this.existingLock = existingLock;
}
/*******************************************************************************
** Fluent setter for existingLock
*******************************************************************************/
public UnableToObtainProcessLockException withExistingLock(ProcessLock existingLock)
{
this.existingLock = existingLock;
return (this);
}
} }

View File

@ -1,72 +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.dashboard.widgets;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.dashboard.RenderWidgetAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.AlertData;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for ProcessAlertWidget
*******************************************************************************/
class ProcessAlertWidgetTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
MetaDataProducerHelper.processAllMetaDataProducersInPackage(QContext.getQInstance(), ProcessAlertWidget.class.getPackageName());
RenderWidgetInput input = new RenderWidgetInput();
input.setWidgetMetaData(QContext.getQInstance().getWidget(ProcessAlertWidget.NAME));
///////////////////////////////////////////////////////////////////////////////////////////
// make sure we run w/o exceptions (and w/ default outputs) if there are no query params //
///////////////////////////////////////////////////////////////////////////////////////////
RenderWidgetOutput output = new RenderWidgetAction().execute(input);
assertEquals(AlertData.AlertType.WARNING, ((AlertData) output.getWidgetData()).getAlertType());
assertEquals("Warning", ((AlertData) output.getWidgetData()).getHtml());
//////////////////////////////////////////////////////
// make sure we input params come through to output //
//////////////////////////////////////////////////////
input.addQueryParam("alertType", "ERROR");
input.addQueryParam("alertHtml", "Do not touch Willy");
output = new RenderWidgetAction().execute(input);
assertEquals(AlertData.AlertType.ERROR, ((AlertData) output.getWidgetData()).getAlertType());
assertEquals("Do not touch Willy", ((AlertData) output.getWidgetData()).getHtml());
}
}

View File

@ -28,7 +28,6 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import com.kingsrook.qqq.backend.core.BaseTest; import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior; import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
@ -39,8 +38,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin; import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
@ -235,7 +232,6 @@ class QInstanceEnricherTest extends BaseTest
} }
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -550,26 +546,4 @@ class QInstanceEnricherTest extends BaseTest
assertEquals(DynamicDefaultValueBehavior.MODIFY_DATE, table.getField("modifyDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class)); assertEquals(DynamicDefaultValueBehavior.MODIFY_DATE, table.getField("modifyDate").getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class));
} }
/*******************************************************************************
**
*******************************************************************************/
@Test
void testOptionalProcessSteps()
{
QInstance qInstance = TestUtils.defineInstance();
QProcessMetaData process = new QProcessMetaData();
process.setName("test");
process.withStepList(List.of(new QBackendStepMetaData().withName("execute").withCode(new QCodeReference(TestUtils.IncreaseBirthdateStep.class))));
process.addOptionalStep(new QFrontendStepMetaData()
.withName("screen")
.withViewField(new QFieldMetaData("myField", QFieldType.STRING)));
qInstance.addProcess(process);
new QInstanceEnricher(qInstance).enrich();
assertEquals("My Field", qInstance.getProcess("test").getFrontendStep("screen").getViewFields().get(0).getLabel());
}
} }

View File

@ -109,8 +109,7 @@ class ProcessLockUtilsTest extends BaseTest
.isInstanceOf(UnableToObtainProcessLockException.class) .isInstanceOf(UnableToObtainProcessLockException.class)
.hasMessageContaining("Held by: " + QContext.getQSession().getUser().getIdReference()) .hasMessageContaining("Held by: " + QContext.getQSession().getUser().getIdReference())
.hasMessageContaining("with details: me") .hasMessageContaining("with details: me")
.hasMessageNotContaining("expiring at: 20") .hasMessageNotContaining("expiring at: 20");
.matches(e -> ((UnableToObtainProcessLockException) e).getExistingLock() != null);
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
// make sure we can create another for a different key // // make sure we can create another for a different key //
@ -180,8 +179,7 @@ class ProcessLockUtilsTest extends BaseTest
.isInstanceOf(UnableToObtainProcessLockException.class) .isInstanceOf(UnableToObtainProcessLockException.class)
.hasMessageContaining("Held by: " + QContext.getQSession().getUser().getIdReference()) .hasMessageContaining("Held by: " + QContext.getQSession().getUser().getIdReference())
.hasMessageContaining("with details: me") .hasMessageContaining("with details: me")
.hasMessageContaining("expiring at: 20") .hasMessageContaining("expiring at: 20");
.matches(e -> ((UnableToObtainProcessLockException) e).getExistingLock() != null);
} }

View File

@ -50,11 +50,6 @@
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version> <version>8.0.30</version>
</dependency> </dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.10.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>

View File

@ -47,18 +47,14 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate; import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy; import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate; import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate;
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByGroupBy; import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext; import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression; import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -139,34 +135,11 @@ public abstract class AbstractRDBMSAction
/******************************************************************************* /*******************************************************************************
** Get a database connection, per the backend in the request. ** Get a database connection, per the backend in the request.
**
** Note that it may be a connection to a read-only backend, per query-hints,
** and backend settings.
*******************************************************************************/ *******************************************************************************/
public static Connection getConnection(AbstractTableActionInput tableActionInput) throws SQLException public static Connection getConnection(AbstractTableActionInput qTableRequest) throws SQLException
{ {
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) tableActionInput.getBackend(); ConnectionManager connectionManager = new ConnectionManager();
return connectionManager.getConnection((RDBMSBackendMetaData) qTableRequest.getBackend());
boolean useReadOnly = false;
if(tableActionInput instanceof QueryInput queryInput)
{
useReadOnly = queryInput.hasQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
}
else if(tableActionInput instanceof CountInput countInput)
{
useReadOnly = countInput.hasQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
}
else if(tableActionInput instanceof AggregateInput aggregateInput)
{
useReadOnly = aggregateInput.hasQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
}
if(useReadOnly && backend.getReadOnlyBackendMetaData() != null)
{
return ConnectionManager.getConnection(backend.getReadOnlyBackendMetaData());
}
return ConnectionManager.getConnection(backend);
} }

View File

@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.io.Serializable; import java.io.Serializable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -57,9 +56,6 @@ 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;
boolean needToCloseConnection = false;
try try
{ {
List<QFieldMetaData> insertableFields = table.getFields().values().stream() List<QFieldMetaData> insertableFields = table.getFields().values().stream()
@ -76,6 +72,8 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
List<QRecord> outputRecords = new ArrayList<>(); List<QRecord> outputRecords = new ArrayList<>();
rs.setRecords(outputRecords); rs.setRecords(outputRecords);
Connection connection;
boolean needToCloseConnection = false;
if(insertInput.getTransaction() != null && insertInput.getTransaction() instanceof RDBMSTransaction rdbmsTransaction) if(insertInput.getTransaction() != null && insertInput.getTransaction() instanceof RDBMSTransaction rdbmsTransaction)
{ {
connection = rdbmsTransaction.getConnection(); connection = rdbmsTransaction.getConnection();
@ -86,6 +84,8 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
needToCloseConnection = true; needToCloseConnection = true;
} }
try
{
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));
@ -158,6 +158,14 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
logSQL(sql, params, mark); logSQL(sql, params, mark);
} }
}
finally
{
if(needToCloseConnection)
{
connection.close();
}
}
return rs; return rs;
} }
@ -165,21 +173,6 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte
{ {
throw new QException("Error executing insert: " + e.getMessage(), e); throw new QException("Error executing insert: " + e.getMessage(), e);
} }
finally
{
if(needToCloseConnection && connection != null)
{
try
{
connection.close();
}
catch(SQLException se)
{
LOG.error("Error closing database connection", se);
}
}
}
} }
} }

View File

@ -41,7 +41,6 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter; import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext; import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
@ -81,6 +80,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
} }
} }
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -361,7 +361,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if we're allowed to use the mysqlResultSetOptimization, and we have the query hint of "expected large result set", then do it. // // if we're allowed to use the mysqlResultSetOptimization, and we have the query hint of "expected large result set", then do it. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(mysqlResultSetOptimizationEnabled && queryInput.getQueryHints() != null && queryInput.getQueryHints().contains(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS)) if(mysqlResultSetOptimizationEnabled && queryInput.getQueryHints() != null && queryInput.getQueryHints().contains(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS))
{ {
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// mysql "optimization", presumably here - from Result Set section of https://dev.mysql.com/doc/connector-j/en/connector-j-reference-implementation-notes.html // // mysql "optimization", presumably here - from Result Set section of https://dev.mysql.com/doc/connector-j/en/connector-j-reference-implementation-notes.html //

View File

@ -1,182 +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.module.rdbms.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.ConnectionPoolSettings;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.json.JSONObject;
import static com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager.getJdbcUrl;
/*******************************************************************************
**
*******************************************************************************/
public class C3P0PooledConnectionProvider implements ConnectionProviderInterface
{
private RDBMSBackendMetaData backend;
private ComboPooledDataSource connectionPool;
private long usageCount = 0;
/*******************************************************************************
**
*******************************************************************************/
@Override
public void init(RDBMSBackendMetaData backend) throws QException
{
this.backend = backend;
try
{
ComboPooledDataSource pool = new ComboPooledDataSource();
pool.setDriverClass(ConnectionManager.getJdbcDriverClassName(backend));
pool.setJdbcUrl(getJdbcUrl(backend));
pool.setUser(backend.getUsername());
pool.setPassword(backend.getPassword());
ConnectionPoolSettings poolSettings = backend.getConnectionPoolSettings();
if(poolSettings != null)
{
if(poolSettings.getInitialPoolSize() != null)
{
pool.setInitialPoolSize(poolSettings.getInitialPoolSize());
}
if(poolSettings.getMinPoolSize() != null)
{
pool.setMinPoolSize(poolSettings.getMinPoolSize());
}
if(poolSettings.getMaxPoolSize() != null)
{
pool.setMaxPoolSize(poolSettings.getMaxPoolSize());
}
if(poolSettings.getAcquireIncrement() != null)
{
pool.setAcquireIncrement(poolSettings.getAcquireIncrement());
}
if(poolSettings.getMaxConnectionAgeSeconds() != null)
{
pool.setMaxConnectionAge(poolSettings.getMaxConnectionAgeSeconds());
}
if(poolSettings.getMaxIdleTimeSeconds() != null)
{
pool.setMaxIdleTime(poolSettings.getMaxIdleTimeSeconds());
}
if(poolSettings.getMaxIdleTimeExcessConnectionsSeconds() != null)
{
pool.setMaxIdleTimeExcessConnections(poolSettings.getMaxIdleTimeExcessConnectionsSeconds());
}
if(poolSettings.getCheckoutTimeoutSeconds() != null)
{
pool.setCheckoutTimeout(poolSettings.getCheckoutTimeoutSeconds() * 1000);
}
if(poolSettings.getTestConnectionOnCheckout() != null)
{
pool.setTestConnectionOnCheckout(poolSettings.getTestConnectionOnCheckout());
}
}
customizePool(pool);
this.connectionPool = pool;
}
catch(Exception e)
{
throw (new QException("Error Initializing C3P0PooledConnectionProvider for backend [" + backend.getName() + "]", e));
}
}
/*******************************************************************************
**
*******************************************************************************/
protected void customizePool(ComboPooledDataSource pool)
{
////////////////////////
// noop in base class //
////////////////////////
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Connection getConnection() throws SQLException
{
usageCount++;
return (this.connectionPool.getConnection());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public JSONObject dumpDebug() throws SQLException
{
JSONObject rs = new JSONObject(new LinkedHashMap<>());
JSONObject settings = new JSONObject(new LinkedHashMap<>());
rs.put("settings", settings);
settings.put("initialPoolSize", connectionPool.getInitialPoolSize());
settings.put("minPoolSize", connectionPool.getMinPoolSize());
settings.put("maxPoolSize", connectionPool.getMaxPoolSize());
settings.put("acquireIncrement", connectionPool.getAcquireIncrement());
settings.put("maxConnectionAge", connectionPool.getMaxConnectionAge());
settings.put("maxIdleTime", connectionPool.getMaxIdleTime());
settings.put("maxIdleTimeExcessConnections", connectionPool.getMaxIdleTimeExcessConnections());
settings.put("checkoutTimeout", connectionPool.getCheckoutTimeout());
settings.put("testConnectionOnCheckout", connectionPool.isTestConnectionOnCheckout());
JSONObject state = new JSONObject(new LinkedHashMap<>());
rs.put("state", state);
state.put("numUsages", usageCount);
state.put("numConnections", connectionPool.getNumConnections());
state.put("numBusyConnections", connectionPool.getNumBusyConnections());
state.put("numIdleConnections", connectionPool.getNumIdleConnections());
state.put("numFailedCheckins", connectionPool.getNumFailedCheckinsDefaultUser());
state.put("numFailedCheckouts", connectionPool.getNumFailedCheckoutsDefaultUser());
state.put("numFailedIdleTests", connectionPool.getNumFailedIdleTestsDefaultUser());
state.put("numThreadsAwaitingCheckout", connectionPool.getNumThreadsAwaitingCheckoutDefaultUser());
return (rs);
}
}

View File

@ -23,110 +23,25 @@ package com.kingsrook.qqq.backend.module.rdbms.jdbc;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.json.JSONArray;
import org.json.JSONObject;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/******************************************************************************* /*******************************************************************************
** Class to manage access to JDBC Connections.
** **
** Relies heavily on RDBMSBackendMetaData.
*******************************************************************************/ *******************************************************************************/
public class ConnectionManager public class ConnectionManager
{ {
private static final QLogger LOG = QLogger.getLogger(ConnectionManager.class);
private static final Map<String, ConnectionProviderInterface> connectionProviderMap = new ConcurrentHashMap<>();
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public static Connection getConnection(RDBMSBackendMetaData backend) throws SQLException public Connection getConnection(RDBMSBackendMetaData backend) throws SQLException
{ {
try String jdbcURL = getJdbcUrl(backend);
{ return DriverManager.getConnection(jdbcURL, backend.getUsername(), backend.getPassword());
ConnectionProviderInterface connectionProvider = getConnectionProvider(backend);
return connectionProvider.getConnection();
}
catch(QException qe)
{
throw (new SQLException("Error getting connection", qe));
}
}
/*******************************************************************************
**
*******************************************************************************/
private static ConnectionProviderInterface getConnectionProvider(RDBMSBackendMetaData backend) throws QException
{
if(!connectionProviderMap.containsKey(backend.getName()))
{
synchronized(connectionProviderMap)
{
if(!connectionProviderMap.containsKey(backend.getName()))
{
QCodeReference connectionProviderReference = backend.getConnectionProvider();
boolean usingDefaultSimpleProvider = false;
if(connectionProviderReference == null)
{
connectionProviderReference = new QCodeReference(SimpleConnectionProvider.class);
usingDefaultSimpleProvider = true;
}
LOG.info("Initializing connection provider for RDBMS backend", logPair("backendName", backend.getName()), logPair("connectionProvider", connectionProviderReference.getName()), logPair("usingDefaultSimpleProvider", usingDefaultSimpleProvider));
ConnectionProviderInterface connectionProvider = QCodeLoader.getAdHoc(ConnectionProviderInterface.class, connectionProviderReference);
connectionProvider.init(backend);
connectionProviderMap.put(backend.getName(), connectionProvider);
}
}
}
return (connectionProviderMap.get(backend.getName()));
}
/*******************************************************************************
**
*******************************************************************************/
public static JSONArray dumpConnectionProviderDebug()
{
try
{
JSONArray rs = new JSONArray();
for(Map.Entry<String, ConnectionProviderInterface> entry : connectionProviderMap.entrySet())
{
JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
jsonObject.put("backendName", entry.getKey());
jsonObject.put("connectionProviderClass", entry.getValue().getClass().getName());
jsonObject.put("values", entry.getValue().dumpDebug());
rs.put(jsonObject);
}
return (rs);
}
catch(Exception e)
{
String message = "Error dumping debug data for connection providers";
LOG.warn(message, e);
return (new JSONArray(new JSONObject(Map.of("error", e.getMessage()))));
}
} }
@ -172,14 +87,4 @@ public class ConnectionManager
}; };
} }
/*******************************************************************************
** reset the map of connection providers - not necessarily meant to be useful
** in production code - written for use in qqq tests.
*******************************************************************************/
static void resetConnectionProviders()
{
connectionProviderMap.clear();
}
} }

View File

@ -1,58 +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.module.rdbms.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.json.JSONObject;
/*******************************************************************************
** interface for classes that can provide jdbc Connections for an RDBMS backend.
*******************************************************************************/
public interface ConnectionProviderInterface
{
/*******************************************************************************
**
*******************************************************************************/
void init(RDBMSBackendMetaData backend) throws QException;
/*******************************************************************************
**
*******************************************************************************/
Connection getConnection() throws SQLException;
/*******************************************************************************
**
*******************************************************************************/
default JSONObject dumpDebug() throws SQLException
{
JSONObject rs = new JSONObject();
rs.put("nothingToReport", true);
return (rs);
}
}

View File

@ -1,63 +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.module.rdbms.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import static com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager.getJdbcUrl;
/*******************************************************************************
** Simple connection provider - no pooling, just opens a new connection for
** every request.
*******************************************************************************/
public class SimpleConnectionProvider implements ConnectionProviderInterface
{
private RDBMSBackendMetaData backend;
/*******************************************************************************
**
*******************************************************************************/
@Override
public void init(RDBMSBackendMetaData backend)
{
this.backend = backend;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Connection getConnection() throws SQLException
{
String jdbcURL = getJdbcUrl(backend);
return DriverManager.getConnection(jdbcURL, backend.getUsername(), backend.getPassword());
}
}

View File

@ -1,326 +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.module.rdbms.model.metadata;
/*******************************************************************************
** Settings for a connection pool (if your backend is configured to use one).
** Originally based on the most common settings for C3P0 - see
** https://www.mchange.com/projects/c3p0/#configuration
**
** If you want more - you'll be looking at defining your own subclass of
** C3P0PooledConnectionProvider and possibly this class.
**
** If using a pool other than C3P0 - some of these may apply others may not.
*******************************************************************************/
public class ConnectionPoolSettings
{
private Integer initialPoolSize;
private Integer minPoolSize;
private Integer maxPoolSize;
private Integer acquireIncrement;
private Integer maxConnectionAgeSeconds;
private Integer maxIdleTimeSeconds;
private Integer maxIdleTimeExcessConnectionsSeconds;
private Integer checkoutTimeoutSeconds;
private Boolean testConnectionOnCheckout;
/*******************************************************************************
** Getter for initialPoolSize
*******************************************************************************/
public Integer getInitialPoolSize()
{
return (this.initialPoolSize);
}
/*******************************************************************************
** Setter for initialPoolSize
*******************************************************************************/
public void setInitialPoolSize(Integer initialPoolSize)
{
this.initialPoolSize = initialPoolSize;
}
/*******************************************************************************
** Fluent setter for initialPoolSize
*******************************************************************************/
public ConnectionPoolSettings withInitialPoolSize(Integer initialPoolSize)
{
this.initialPoolSize = initialPoolSize;
return (this);
}
/*******************************************************************************
** Getter for minPoolSize
*******************************************************************************/
public Integer getMinPoolSize()
{
return (this.minPoolSize);
}
/*******************************************************************************
** Setter for minPoolSize
*******************************************************************************/
public void setMinPoolSize(Integer minPoolSize)
{
this.minPoolSize = minPoolSize;
}
/*******************************************************************************
** Fluent setter for minPoolSize
*******************************************************************************/
public ConnectionPoolSettings withMinPoolSize(Integer minPoolSize)
{
this.minPoolSize = minPoolSize;
return (this);
}
/*******************************************************************************
** Getter for maxPoolSize
*******************************************************************************/
public Integer getMaxPoolSize()
{
return (this.maxPoolSize);
}
/*******************************************************************************
** Setter for maxPoolSize
*******************************************************************************/
public void setMaxPoolSize(Integer maxPoolSize)
{
this.maxPoolSize = maxPoolSize;
}
/*******************************************************************************
** Fluent setter for maxPoolSize
*******************************************************************************/
public ConnectionPoolSettings withMaxPoolSize(Integer maxPoolSize)
{
this.maxPoolSize = maxPoolSize;
return (this);
}
/*******************************************************************************
** Getter for acquireIncrement
*******************************************************************************/
public Integer getAcquireIncrement()
{
return (this.acquireIncrement);
}
/*******************************************************************************
** Setter for acquireIncrement
*******************************************************************************/
public void setAcquireIncrement(Integer acquireIncrement)
{
this.acquireIncrement = acquireIncrement;
}
/*******************************************************************************
** Fluent setter for acquireIncrement
*******************************************************************************/
public ConnectionPoolSettings withAcquireIncrement(Integer acquireIncrement)
{
this.acquireIncrement = acquireIncrement;
return (this);
}
/*******************************************************************************
** Getter for maxConnectionAgeSeconds
*******************************************************************************/
public Integer getMaxConnectionAgeSeconds()
{
return (this.maxConnectionAgeSeconds);
}
/*******************************************************************************
** Setter for maxConnectionAgeSeconds
*******************************************************************************/
public void setMaxConnectionAgeSeconds(Integer maxConnectionAgeSeconds)
{
this.maxConnectionAgeSeconds = maxConnectionAgeSeconds;
}
/*******************************************************************************
** Fluent setter for maxConnectionAgeSeconds
*******************************************************************************/
public ConnectionPoolSettings withMaxConnectionAgeSeconds(Integer maxConnectionAgeSeconds)
{
this.maxConnectionAgeSeconds = maxConnectionAgeSeconds;
return (this);
}
/*******************************************************************************
** Getter for maxIdleTimeSeconds
*******************************************************************************/
public Integer getMaxIdleTimeSeconds()
{
return (this.maxIdleTimeSeconds);
}
/*******************************************************************************
** Setter for maxIdleTimeSeconds
*******************************************************************************/
public void setMaxIdleTimeSeconds(Integer maxIdleTimeSeconds)
{
this.maxIdleTimeSeconds = maxIdleTimeSeconds;
}
/*******************************************************************************
** Fluent setter for maxIdleTimeSeconds
*******************************************************************************/
public ConnectionPoolSettings withMaxIdleTimeSeconds(Integer maxIdleTimeSeconds)
{
this.maxIdleTimeSeconds = maxIdleTimeSeconds;
return (this);
}
/*******************************************************************************
** Getter for maxIdleTimeExcessConnectionsSeconds
*******************************************************************************/
public Integer getMaxIdleTimeExcessConnectionsSeconds()
{
return (this.maxIdleTimeExcessConnectionsSeconds);
}
/*******************************************************************************
** Setter for maxIdleTimeExcessConnectionsSeconds
*******************************************************************************/
public void setMaxIdleTimeExcessConnectionsSeconds(Integer maxIdleTimeExcessConnectionsSeconds)
{
this.maxIdleTimeExcessConnectionsSeconds = maxIdleTimeExcessConnectionsSeconds;
}
/*******************************************************************************
** Fluent setter for maxIdleTimeExcessConnectionsSeconds
*******************************************************************************/
public ConnectionPoolSettings withMaxIdleTimeExcessConnectionsSeconds(Integer maxIdleTimeExcessConnectionsSeconds)
{
this.maxIdleTimeExcessConnectionsSeconds = maxIdleTimeExcessConnectionsSeconds;
return (this);
}
/*******************************************************************************
** Getter for testConnectionOnCheckout
*******************************************************************************/
public Boolean getTestConnectionOnCheckout()
{
return (this.testConnectionOnCheckout);
}
/*******************************************************************************
** Setter for testConnectionOnCheckout
*******************************************************************************/
public void setTestConnectionOnCheckout(Boolean testConnectionOnCheckout)
{
this.testConnectionOnCheckout = testConnectionOnCheckout;
}
/*******************************************************************************
** Fluent setter for testConnectionOnCheckout
*******************************************************************************/
public ConnectionPoolSettings withTestConnectionOnCheckout(Boolean testConnectionOnCheckout)
{
this.testConnectionOnCheckout = testConnectionOnCheckout;
return (this);
}
/*******************************************************************************
** Getter for checkoutTimeoutSeconds
*******************************************************************************/
public Integer getCheckoutTimeoutSeconds()
{
return (this.checkoutTimeoutSeconds);
}
/*******************************************************************************
** Setter for checkoutTimeoutSeconds
*******************************************************************************/
public void setCheckoutTimeoutSeconds(Integer checkoutTimeoutSeconds)
{
this.checkoutTimeoutSeconds = checkoutTimeoutSeconds;
}
/*******************************************************************************
** Fluent setter for checkoutTimeoutSeconds
*******************************************************************************/
public ConnectionPoolSettings withCheckoutTimeoutSeconds(Integer checkoutTimeoutSeconds)
{
this.checkoutTimeoutSeconds = checkoutTimeoutSeconds;
return (this);
}
}

View File

@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.module.rdbms.model.metadata;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter; import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule; import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule;
@ -43,12 +42,6 @@ public class RDBMSBackendMetaData extends QBackendMetaData
private String jdbcUrl; private String jdbcUrl;
private String jdbcDriverClassName; private String jdbcDriverClassName;
private QCodeReference connectionProvider;
private ConnectionPoolSettings connectionPoolSettings;
private RDBMSBackendMetaData readOnlyBackendMetaData;
/******************************************************************************* /*******************************************************************************
@ -323,7 +316,6 @@ public class RDBMSBackendMetaData extends QBackendMetaData
} }
/******************************************************************************* /*******************************************************************************
** Getter for jdbcDriverClassName ** Getter for jdbcDriverClassName
*******************************************************************************/ *******************************************************************************/
@ -354,96 +346,4 @@ public class RDBMSBackendMetaData extends QBackendMetaData
} }
/*******************************************************************************
** Getter for connectionProvider
*******************************************************************************/
public QCodeReference getConnectionProvider()
{
return (this.connectionProvider);
}
/*******************************************************************************
** Setter for connectionProvider
*******************************************************************************/
public void setConnectionProvider(QCodeReference connectionProvider)
{
this.connectionProvider = connectionProvider;
}
/*******************************************************************************
** Fluent setter for connectionProvider
*******************************************************************************/
public RDBMSBackendMetaData withConnectionProvider(QCodeReference connectionProvider)
{
this.connectionProvider = connectionProvider;
return (this);
}
/*******************************************************************************
** Getter for readOnlyBackendMetaData
*******************************************************************************/
public RDBMSBackendMetaData getReadOnlyBackendMetaData()
{
return (this.readOnlyBackendMetaData);
}
/*******************************************************************************
** Setter for readOnlyBackendMetaData
*******************************************************************************/
public void setReadOnlyBackendMetaData(RDBMSBackendMetaData readOnlyBackendMetaData)
{
this.readOnlyBackendMetaData = readOnlyBackendMetaData;
}
/*******************************************************************************
** Fluent setter for readOnlyBackendMetaData
*******************************************************************************/
public RDBMSBackendMetaData withReadOnlyBackendMetaData(RDBMSBackendMetaData readOnlyBackendMetaData)
{
this.readOnlyBackendMetaData = readOnlyBackendMetaData;
return (this);
}
/*******************************************************************************
** Getter for connectionPoolSettings
*******************************************************************************/
public ConnectionPoolSettings getConnectionPoolSettings()
{
return (this.connectionPoolSettings);
}
/*******************************************************************************
** Setter for connectionPoolSettings
*******************************************************************************/
public void setConnectionPoolSettings(ConnectionPoolSettings connectionPoolSettings)
{
this.connectionPoolSettings = connectionPoolSettings;
}
/*******************************************************************************
** Fluent setter for connectionPoolSettings
*******************************************************************************/
public RDBMSBackendMetaData withConnectionPoolSettings(ConnectionPoolSettings connectionPoolSettings)
{
this.connectionPoolSettings = connectionPoolSettings;
return (this);
}
} }

View File

@ -67,6 +67,5 @@ public class RDBMSActionTest extends BaseTest
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(TestUtils.defineBackend()); Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
QueryManager.executeStatement(connection, sql, resultSetProcessor); QueryManager.executeStatement(connection, sql, resultSetProcessor);
connection.close();
} }
} }

View File

@ -1,237 +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.module.rdbms.jdbc;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import com.kingsrook.qqq.backend.module.rdbms.BaseTest;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.ConnectionPoolSettings;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.mchange.v2.resourcepool.TimeoutException;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/*******************************************************************************
** Unit test for C3P0PooledConnectionProvider
*******************************************************************************/
class C3P0PooledConnectionProviderTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws Exception
{
TestUtils.primeTestDatabase("prime-test-database.sql");
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// must call this after the primeTestDatabase call (as i uses a raw version of the backend, w/o our updated settings) //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ConnectionManager.resetConnectionProviders();
}
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
////////////////////////////////////////////////////////////
// just for good measure, do this after each test in here //
////////////////////////////////////////////////////////////
ConnectionManager.resetConnectionProviders();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
// @RepeatedTest(100)
void test() throws Exception
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// change the default database backend to use the class under test here - the C3PL connection pool provider //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QInstance qInstance = TestUtils.defineInstance();
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) qInstance.getBackend(TestUtils.DEFAULT_BACKEND_NAME);
backend.setConnectionProvider(new QCodeReference(C3P0PooledConnectionProvider.class));
QContext.init(qInstance, new QSession());
for(int i = 0; i < 5; i++)
{
new QueryAction().execute(new QueryInput(TestUtils.TABLE_NAME_PERSON));
}
JSONObject debugValues = getDebugStateValues(true);
assertThat(debugValues.getInt("numConnections")).isEqualTo(3); // one time (in a @RepeatedTest(100) we saw a 3 != 6 here...)
////////////////////////////////////////////////////////////////////
// open up 4 transactions - confirm the pool opens some new conns //
////////////////////////////////////////////////////////////////////
List<QBackendTransaction> transactions = new ArrayList<>();
for(int i = 0; i < 5; i++)
{
transactions.add(QBackendTransaction.openFor(new InsertInput(TestUtils.TABLE_NAME_PERSON)));
}
debugValues = getDebugStateValues(true);
assertThat(debugValues.getInt("numConnections")).isGreaterThan(3);
transactions.forEach(transaction -> transaction.close());
/////////////////////////////////////////////////////////////////////////
// might take a second for the pool to re-claim the closed connections //
/////////////////////////////////////////////////////////////////////////
boolean foundMatch = false;
for(int i = 0; i < 5; i++)
{
debugValues = getDebugStateValues(true);
if(debugValues.getInt("numConnections") == debugValues.getInt("numIdleConnections"))
{
foundMatch = true;
break;
}
System.out.println("oops!");
SleepUtils.sleep(250, TimeUnit.MILLISECONDS);
}
assertTrue(foundMatch, "The pool didn't re-claim all connections...");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPoolSettings() throws Exception
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// change the default database backend to use the class under test here - the C3PL connection pool provider //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QInstance qInstance = TestUtils.defineInstance();
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) qInstance.getBackend(TestUtils.DEFAULT_BACKEND_NAME);
backend.setConnectionProvider(new QCodeReference(C3P0PooledConnectionProvider.class));
backend.setConnectionPoolSettings(new ConnectionPoolSettings()
.withInitialPoolSize(2)
.withAcquireIncrement(1)
.withMinPoolSize(1)
.withMaxPoolSize(4)
.withCheckoutTimeoutSeconds(1));
QContext.init(qInstance, new QSession());
/////////////////////////
// assert initial size //
/////////////////////////
new QueryAction().execute(new QueryInput(TestUtils.TABLE_NAME_PERSON));
JSONObject debugValues = getDebugStateValues(true);
assertThat(debugValues.getInt("numConnections")).isEqualTo(2);
///////////////////////////////////////////////////////////////////////
// open (and close) 5 conns - shouldn't get bigger than initial size //
///////////////////////////////////////////////////////////////////////
for(int i = 0; i < 5; i++)
{
new QueryAction().execute(new QueryInput(TestUtils.TABLE_NAME_PERSON));
}
debugValues = getDebugStateValues(true);
assertThat(debugValues.getInt("numConnections")).isEqualTo(2); // one time (in a @RepeatedTest(100) we saw a 3 != 6 here...)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// open up 4 transactions - confirm the pool opens some new conns, but stops at the max, and throws based on checkoutTimeout setting //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
List<QBackendTransaction> transactions = new ArrayList<>();
for(int i = 0; i < 5; i++)
{
if(i == 4)
{
//////////////////////////////////////////
// expect this one to fail - full pool! //
//////////////////////////////////////////
assertThatThrownBy(() -> QBackendTransaction.openFor(new InsertInput(TestUtils.TABLE_NAME_PERSON)))
.hasRootCauseInstanceOf(TimeoutException.class);
}
else
{
transactions.add(QBackendTransaction.openFor(new InsertInput(TestUtils.TABLE_NAME_PERSON)));
}
}
debugValues = getDebugStateValues(true);
assertThat(debugValues.getInt("numConnections")).isEqualTo(4);
}
/*******************************************************************************
**
*******************************************************************************/
private static JSONObject getDebugStateValues(boolean printIt)
{
JSONArray debugArray = ConnectionManager.dumpConnectionProviderDebug();
for(int i = 0; i < debugArray.length(); i++)
{
JSONObject object = debugArray.getJSONObject(i);
if(TestUtils.DEFAULT_BACKEND_NAME.equals(object.optString("backendName")))
{
JSONObject values = object.getJSONObject("values");
if(printIt)
{
System.out.println(values.toString(3));
}
JSONObject state = values.getJSONObject("state");
return state;
}
}
fail("Didn't find debug values...");
return (null);
}
}

View File

@ -64,8 +64,7 @@ class QueryManagerTest extends BaseTest
@BeforeEach @BeforeEach
void beforeEach() throws SQLException void beforeEach() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
QueryManager.executeUpdate(connection, """ QueryManager.executeUpdate(connection, """
CREATE TABLE test_table CREATE TABLE test_table
( (
@ -78,7 +77,6 @@ class QueryManagerTest extends BaseTest
) )
"""); """);
} }
}
@ -88,11 +86,9 @@ class QueryManagerTest extends BaseTest
@AfterEach @AfterEach
void afterEach() throws SQLException void afterEach() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
QueryManager.executeUpdate(connection, "DROP TABLE test_table"); QueryManager.executeUpdate(connection, "DROP TABLE test_table");
} }
}
@ -112,10 +108,9 @@ class QueryManagerTest extends BaseTest
*******************************************************************************/ *******************************************************************************/
@Test @Test
void testBindParams() throws SQLException void testBindParams() throws SQLException
{
try(Connection connection = getConnection())
{ {
long ctMillis = System.currentTimeMillis(); long ctMillis = System.currentTimeMillis();
Connection connection = getConnection();
PreparedStatement ps = connection.prepareStatement("UPDATE test_table SET int_col = ? WHERE int_col > 0"); PreparedStatement ps = connection.prepareStatement("UPDATE test_table SET int_col = ? WHERE int_col > 0");
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -165,7 +160,6 @@ class QueryManagerTest extends BaseTest
// originally longs were being downgraded to int when binding, so, verify that doesn't happen // // originally longs were being downgraded to int when binding, so, verify that doesn't happen //
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
} }
}
@ -174,11 +168,10 @@ class QueryManagerTest extends BaseTest
*******************************************************************************/ *******************************************************************************/
@Test @Test
void testLongBinding() throws SQLException void testLongBinding() throws SQLException
{
try(Connection connection = getConnection())
{ {
Long biggerThanMaxInteger = 2147483648L; Long biggerThanMaxInteger = 2147483648L;
Connection connection = getConnection();
PreparedStatement ps = connection.prepareStatement("INSERT INTO test_table (long_col) VALUES (?)"); PreparedStatement ps = connection.prepareStatement("INSERT INTO test_table (long_col) VALUES (?)");
QueryManager.bindParam(ps, 1, biggerThanMaxInteger); QueryManager.bindParam(ps, 1, biggerThanMaxInteger);
ps.execute(); ps.execute();
@ -190,7 +183,6 @@ class QueryManagerTest extends BaseTest
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(biggerThanMaxInteger, QueryManager.getLong(rs, "long_col")); assertEquals(biggerThanMaxInteger, QueryManager.getLong(rs, "long_col"));
} }
}
@ -199,11 +191,10 @@ class QueryManagerTest extends BaseTest
*******************************************************************************/ *******************************************************************************/
@Test @Test
void testGetValueMethods() throws SQLException void testGetValueMethods() throws SQLException
{
try(Connection connection = getConnection())
{ {
Long biggerThanMaxInteger = 2147483648L; Long biggerThanMaxInteger = 2147483648L;
Connection connection = getConnection();
QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, datetime_col, char_col, long_col) VALUES (1, now(), 'A', " + biggerThanMaxInteger + ")"); QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, datetime_col, char_col, long_col) VALUES (1, now(), 'A', " + biggerThanMaxInteger + ")");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT int_col, datetime_col, char_col, long_col from test_table"); PreparedStatement preparedStatement = connection.prepareStatement("SELECT int_col, datetime_col, char_col, long_col from test_table");
preparedStatement.execute(); preparedStatement.execute();
@ -239,7 +230,6 @@ class QueryManagerTest extends BaseTest
assertEquals(biggerThanMaxInteger, QueryManager.getLong(rs, "long_col")); assertEquals(biggerThanMaxInteger, QueryManager.getLong(rs, "long_col"));
assertEquals(biggerThanMaxInteger, QueryManager.getLong(rs, 4)); assertEquals(biggerThanMaxInteger, QueryManager.getLong(rs, 4));
} }
}
@ -249,8 +239,7 @@ class QueryManagerTest extends BaseTest
@Test @Test
void testGetValueMethodsReturningNull() throws SQLException void testGetValueMethodsReturningNull() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, datetime_col, char_col) VALUES (null, null, null)"); QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, datetime_col, char_col) VALUES (null, null, null)");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * from test_table"); PreparedStatement preparedStatement = connection.prepareStatement("SELECT * from test_table");
preparedStatement.execute(); preparedStatement.execute();
@ -284,7 +273,6 @@ class QueryManagerTest extends BaseTest
assertNull(QueryManager.getObject(rs, "char_col")); assertNull(QueryManager.getObject(rs, "char_col"));
assertNull(QueryManager.getObject(rs, 3)); assertNull(QueryManager.getObject(rs, 3));
} }
}
@ -295,8 +283,7 @@ class QueryManagerTest extends BaseTest
@Test @Test
void testLocalDate() throws SQLException void testLocalDate() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
QueryManager.executeUpdate(connection, "INSERT INTO test_table (date_col) VALUES (?)", LocalDate.of(2013, Month.OCTOBER, 1)); QueryManager.executeUpdate(connection, "INSERT INTO test_table (date_col) VALUES (?)", LocalDate.of(2013, Month.OCTOBER, 1));
PreparedStatement preparedStatement = connection.prepareStatement("SELECT date_col from test_table"); PreparedStatement preparedStatement = connection.prepareStatement("SELECT date_col from test_table");
@ -328,7 +315,6 @@ class QueryManagerTest extends BaseTest
assertEquals(0, offsetDateTime.getHour(), "Hour value"); assertEquals(0, offsetDateTime.getHour(), "Hour value");
assertEquals(0, offsetDateTime.getMinute(), "Minute value"); assertEquals(0, offsetDateTime.getMinute(), "Minute value");
} }
}
@ -338,8 +324,8 @@ class QueryManagerTest extends BaseTest
@Test @Test
void testLocalTime() throws SQLException void testLocalTime() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
//////////////////////////////////// ////////////////////////////////////
// insert one just hour & minutes // // insert one just hour & minutes //
//////////////////////////////////// ////////////////////////////////////
@ -380,7 +366,6 @@ class QueryManagerTest extends BaseTest
assertEquals(42, localTime.getMinute(), "Minute value"); assertEquals(42, localTime.getMinute(), "Minute value");
assertEquals(59, localTime.getSecond(), "Second value"); assertEquals(59, localTime.getSecond(), "Second value");
} }
}
@ -390,8 +375,7 @@ class QueryManagerTest extends BaseTest
@Test @Test
void testExecuteStatementForSingleValue() throws SQLException void testExecuteStatementForSingleValue() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
QueryManager.executeUpdate(connection, """ QueryManager.executeUpdate(connection, """
INSERT INTO test_table INSERT INTO test_table
( int_col, datetime_col, char_col, date_col, time_col ) ( int_col, datetime_col, char_col, date_col, time_col )
@ -413,7 +397,6 @@ class QueryManagerTest extends BaseTest
"""); """);
assertEquals(null, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT int_col FROM test_table WHERE int_col IS NULL")); assertEquals(null, QueryManager.executeStatementForSingleValue(connection, Integer.class, "SELECT int_col FROM test_table WHERE int_col IS NULL"));
} }
}
@ -423,8 +406,7 @@ class QueryManagerTest extends BaseTest
@Test @Test
void testQueryForSimpleEntity() throws SQLException void testQueryForSimpleEntity() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
QueryManager.executeUpdate(connection, """ QueryManager.executeUpdate(connection, """
INSERT INTO test_table INSERT INTO test_table
( int_col, datetime_col, char_col, date_col, time_col ) ( int_col, datetime_col, char_col, date_col, time_col )
@ -436,7 +418,6 @@ class QueryManagerTest extends BaseTest
assertEquals(47, simpleEntity.get("INT_COL")); assertEquals(47, simpleEntity.get("INT_COL"));
assertEquals("Q", simpleEntity.get("CHAR_COL")); assertEquals("Q", simpleEntity.get("CHAR_COL"));
} }
}
@ -446,8 +427,7 @@ class QueryManagerTest extends BaseTest
@Test @Test
void testQueryForRows() throws SQLException void testQueryForRows() throws SQLException
{ {
try(Connection connection = getConnection()) Connection connection = getConnection();
{
QueryManager.executeUpdate(connection, """ QueryManager.executeUpdate(connection, """
INSERT INTO test_table INSERT INTO test_table
( int_col, datetime_col, char_col, date_col, time_col ) ( int_col, datetime_col, char_col, date_col, time_col )
@ -459,6 +439,5 @@ class QueryManagerTest extends BaseTest
assertEquals(47, rows.get(0).get("INT_COL")); assertEquals(47, rows.get(0).get("INT_COL"));
assertEquals("Q", rows.get(0).get("CHAR_COL")); assertEquals("Q", rows.get(0).get("CHAR_COL"));
} }
}
} }

View File

@ -9,4 +9,3 @@ qqq-middleware-picocli
qqq-middleware-slack qqq-middleware-slack
qqq-middleware-api qqq-middleware-api
qqq-frontend-material-dashboard qqq-frontend-material-dashboard
qqq-bom-pom

View File

@ -82,7 +82,6 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination; 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.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource; import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
@ -1196,7 +1195,6 @@ public class QJavalinImplementation
countInput.setTimeoutSeconds(DEFAULT_COUNT_TIMEOUT_SECONDS); countInput.setTimeoutSeconds(DEFAULT_COUNT_TIMEOUT_SECONDS);
countInput.setQueryJoins(processQueryJoinsParam(context)); countInput.setQueryJoins(processQueryJoinsParam(context));
countInput.setIncludeDistinctCount(QJavalinUtils.queryParamIsTrue(context, "includeDistinct")); countInput.setIncludeDistinctCount(QJavalinUtils.queryParamIsTrue(context, "includeDistinct"));
countInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
CountAction countAction = new CountAction(); CountAction countAction = new CountAction();
CountOutput countOutput = countAction.execute(countInput); CountOutput countOutput = countAction.execute(countInput);
@ -1252,7 +1250,6 @@ public class QJavalinImplementation
queryInput.setShouldGenerateDisplayValues(true); queryInput.setShouldGenerateDisplayValues(true);
queryInput.setShouldTranslatePossibleValues(true); queryInput.setShouldTranslatePossibleValues(true);
queryInput.setTimeoutSeconds(DEFAULT_QUERY_TIMEOUT_SECONDS); queryInput.setTimeoutSeconds(DEFAULT_QUERY_TIMEOUT_SECONDS);
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ); PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ);

View File

@ -114,9 +114,7 @@ public class TestUtils
public static void primeTestDatabase() throws Exception public static void primeTestDatabase() throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(TestUtils.defineDefaultH2Backend());
try(Connection connection = connectionManager.getConnection(TestUtils.defineDefaultH2Backend()))
{
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
assertNotNull(primeTestDatabaseSqlStream); assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream); List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
@ -127,7 +125,6 @@ public class TestUtils
QueryManager.executeUpdate(connection, sql); QueryManager.executeUpdate(connection, sql);
} }
} }
}
@ -138,11 +135,9 @@ public class TestUtils
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
try(Connection connection = connectionManager.getConnection(defineDefaultH2Backend())) Connection connection = connectionManager.getConnection(defineDefaultH2Backend());
{
QueryManager.executeStatement(connection, sql, resultSetProcessor); QueryManager.executeStatement(connection, sql, resultSetProcessor);
} }
}

View File

@ -61,8 +61,7 @@ public class TestUtils
public static void primeTestDatabase() throws Exception public static void primeTestDatabase() throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
try(Connection connection = connectionManager.getConnection(TestUtils.defineBackend())) Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
{
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
assertNotNull(primeTestDatabaseSqlStream); assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream); List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
@ -73,7 +72,6 @@ public class TestUtils
QueryManager.executeUpdate(connection, sql); QueryManager.executeUpdate(connection, sql);
} }
} }
}
@ -84,11 +82,9 @@ public class TestUtils
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
try(Connection connection = connectionManager.getConnection(defineBackend())) Connection connection = connectionManager.getConnection(defineBackend());
{
QueryManager.executeStatement(connection, sql, resultSetProcessor); QueryManager.executeStatement(connection, sql, resultSetProcessor);
} }
}