mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
checkpoint - working version of c3p0 connection pooling, and read-only database meta-data connections (per query hint)
This commit is contained in:
@ -44,6 +44,7 @@ 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.ExportOutput;
|
||||
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.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -216,7 +217,8 @@ public class ExportAction
|
||||
}
|
||||
queryInput.getFilter().setLimit(exportInput.getLimit());
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(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 //
|
||||
|
@ -59,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.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.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||
@ -417,7 +418,8 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
queryInput.setTableName(dataSource.getSourceTable());
|
||||
queryInput.setFilter(queryFilter);
|
||||
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||
queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter())));
|
||||
|
@ -32,13 +32,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
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.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -78,45 +77,6 @@ public class QPossibleValueTranslator
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -601,7 +561,7 @@ public class QPossibleValueTranslator
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(idField, QCriteriaOperator.IN, page)));
|
||||
queryInput.setTransaction(getTransaction(tableName));
|
||||
queryInput.hasQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// when querying for possible values, we do want to generate their display values, which makes record labels, which are usually used as PVS labels //
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
@ -23,8 +23,10 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.aggregate;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
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.QueryJoin;
|
||||
|
||||
@ -44,6 +46,8 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
|
||||
private EnumSet<QueryHint> queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -302,4 +306,78 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,10 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.count;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
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.QueryJoin;
|
||||
|
||||
@ -42,6 +44,8 @@ public class CountInput extends AbstractTableActionInput
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
private Boolean includeDistinctCount = false;
|
||||
|
||||
private EnumSet<QueryHint> queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -207,4 +211,78 @@ public class CountInput extends AbstractTableActionInput
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -31,12 +31,16 @@ import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** 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
|
||||
{
|
||||
@ -74,22 +78,6 @@ 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -683,4 +671,19 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.garbagecollecto
|
||||
import java.time.Instant;
|
||||
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.tables.QueryHint;
|
||||
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.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
||||
@ -66,7 +67,7 @@ public class GarbageCollectorExtractStep extends ExtractViaQueryStep
|
||||
@Override
|
||||
protected void customizeInputPreQuery(QueryInput queryInput)
|
||||
{
|
||||
queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,14 +47,18 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
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.tables.QueryHint;
|
||||
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.QFilterOrderByAggregate;
|
||||
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.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -135,11 +139,34 @@ public abstract class AbstractRDBMSAction
|
||||
|
||||
/*******************************************************************************
|
||||
** 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 qTableRequest) throws SQLException
|
||||
public static Connection getConnection(AbstractTableActionInput tableActionInput) throws SQLException
|
||||
{
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
return connectionManager.getConnection((RDBMSBackendMetaData) qTableRequest.getBackend());
|
||||
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) tableActionInput.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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
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.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
@ -80,7 +81,6 @@ 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(mysqlResultSetOptimizationEnabled && queryInput.getQueryHints() != null && queryInput.getQueryHints().contains(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS))
|
||||
if(mysqlResultSetOptimizationEnabled && queryInput.getQueryHints() != null && queryInput.getQueryHints().contains(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 //
|
||||
|
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
@ -23,68 +23,81 @@ package com.kingsrook.qqq.backend.module.rdbms.jdbc;
|
||||
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
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.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import com.mchange.v2.c3p0.ComboPooledDataSource;
|
||||
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
|
||||
{
|
||||
private boolean mayUseConnectionPool = true;
|
||||
private static final QLogger LOG = QLogger.getLogger(ConnectionManager.class);
|
||||
|
||||
private static Map<String, Boolean> initedConnectionPool = new HashMap<>();
|
||||
private static Map<String, ComboPooledDataSource> connectionPoolMap = new HashMap<>();
|
||||
private static final Map<String, ConnectionProviderInterface> connectionProviderMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static int usageCounter = 0;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Connection getConnection(RDBMSBackendMetaData backend) throws SQLException
|
||||
{
|
||||
usageCounter++;
|
||||
|
||||
if(mayUseConnectionPool)
|
||||
{
|
||||
return (getConnectionFromPool(backend));
|
||||
}
|
||||
|
||||
String jdbcURL = getJdbcUrl(backend);
|
||||
return DriverManager.getConnection(jdbcURL, backend.getUsername(), backend.getPassword());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void checkPools()
|
||||
public static Connection getConnection(RDBMSBackendMetaData backend) throws SQLException
|
||||
{
|
||||
try
|
||||
{
|
||||
System.out.println("Usages: " + usageCounter);
|
||||
ConnectionProviderInterface connectionProvider = getConnectionProvider(backend);
|
||||
return connectionProvider.getConnection();
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (new SQLException("Error getting connection", qe));
|
||||
}
|
||||
}
|
||||
|
||||
for(Map.Entry<String, ComboPooledDataSource> entry : CollectionUtils.nonNullMap(connectionPoolMap).entrySet())
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static ConnectionProviderInterface getConnectionProvider(RDBMSBackendMetaData backend) throws QException
|
||||
{
|
||||
if(!connectionProviderMap.containsKey(backend.getName()))
|
||||
{
|
||||
synchronized(connectionProviderMap)
|
||||
{
|
||||
System.out.println("POOL USAGE: " + entry.getKey() + ": " + entry.getValue().getNumBusyConnections());
|
||||
if(entry.getValue().getNumBusyConnections() > 2)
|
||||
if(!connectionProviderMap.containsKey(backend.getName()))
|
||||
{
|
||||
System.out.println("break!");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return (connectionProviderMap.get(backend.getName()));
|
||||
}
|
||||
|
||||
|
||||
@ -92,36 +105,27 @@ public class ConnectionManager
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Connection getConnectionFromPool(RDBMSBackendMetaData backend) throws SQLException
|
||||
public static JSONArray dumpConnectionProviderDebug()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!initedConnectionPool.getOrDefault(backend.getName(), false))
|
||||
JSONArray rs = new JSONArray();
|
||||
for(Map.Entry<String, ConnectionProviderInterface> entry : connectionProviderMap.entrySet())
|
||||
{
|
||||
// todo - some syncrhonized
|
||||
ComboPooledDataSource connectionPool = new ComboPooledDataSource();
|
||||
connectionPool.setDriverClass(getJdbcDriverClassName(backend));
|
||||
connectionPool.setJdbcUrl(getJdbcUrl(backend));
|
||||
connectionPool.setUser(backend.getUsername());
|
||||
connectionPool.setPassword(backend.getPassword());
|
||||
|
||||
connectionPool.setTestConnectionOnCheckout(true);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// useful to debug leaking connections - meant for tests only though... //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// connectionPool.setDebugUnreturnedConnectionStackTraces(true);
|
||||
// connectionPool.setUnreturnedConnectionTimeout(10);
|
||||
|
||||
connectionPoolMap.put(backend.getName(), connectionPool);
|
||||
initedConnectionPool.put(backend.getName(), true);
|
||||
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 (connectionPoolMap.get(backend.getName()).getConnection());
|
||||
return (rs);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new SQLException("Error getting connection from pool", e));
|
||||
String message = "Error dumping debug data for connection providers";
|
||||
LOG.warn(message, e);
|
||||
return (new JSONArray(new JSONObject(Map.of("error", e.getMessage()))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,4 +172,14 @@ 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();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,326 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.module.rdbms.model.metadata;
|
||||
|
||||
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.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule;
|
||||
|
||||
|
||||
@ -42,6 +43,12 @@ public class RDBMSBackendMetaData extends QBackendMetaData
|
||||
private String jdbcUrl;
|
||||
private String jdbcDriverClassName;
|
||||
|
||||
private QCodeReference connectionProvider;
|
||||
|
||||
private ConnectionPoolSettings connectionPoolSettings;
|
||||
|
||||
private RDBMSBackendMetaData readOnlyBackendMetaData;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -316,6 +323,7 @@ public class RDBMSBackendMetaData extends QBackendMetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for jdbcDriverClassName
|
||||
*******************************************************************************/
|
||||
@ -346,4 +354,96 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
@ -82,6 +82,7 @@ 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.ReportFormat;
|
||||
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.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
@ -1195,6 +1196,7 @@ public class QJavalinImplementation
|
||||
countInput.setTimeoutSeconds(DEFAULT_COUNT_TIMEOUT_SECONDS);
|
||||
countInput.setQueryJoins(processQueryJoinsParam(context));
|
||||
countInput.setIncludeDistinctCount(QJavalinUtils.queryParamIsTrue(context, "includeDistinct"));
|
||||
countInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
CountAction countAction = new CountAction();
|
||||
CountOutput countOutput = countAction.execute(countInput);
|
||||
@ -1250,6 +1252,7 @@ public class QJavalinImplementation
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setTimeoutSeconds(DEFAULT_QUERY_TIMEOUT_SECONDS);
|
||||
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ);
|
||||
|
||||
|
Reference in New Issue
Block a user