mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
@ -67,4 +67,15 @@ public interface BaseQueryInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
default void cancelAction()
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// initially at least, a noop in base class //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
@ -41,6 +42,12 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class AggregateAction
|
public class AggregateAction
|
||||||
{
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(AggregateAction.class);
|
||||||
|
|
||||||
|
private AggregateInterface aggregateInterface;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -56,7 +63,7 @@ public class AggregateAction
|
|||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(aggregateInput.getBackend());
|
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(aggregateInput.getBackend());
|
||||||
|
|
||||||
AggregateInterface aggregateInterface = qModule.getAggregateInterface();
|
aggregateInterface = qModule.getAggregateInterface();
|
||||||
aggregateInterface.setQueryStat(queryStat);
|
aggregateInterface.setQueryStat(queryStat);
|
||||||
AggregateOutput aggregateOutput = aggregateInterface.execute(aggregateInput);
|
AggregateOutput aggregateOutput = aggregateInterface.execute(aggregateInput);
|
||||||
|
|
||||||
@ -64,4 +71,20 @@ public class AggregateAction
|
|||||||
|
|
||||||
return aggregateOutput;
|
return aggregateOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void cancel()
|
||||||
|
{
|
||||||
|
if(aggregateInterface == null)
|
||||||
|
{
|
||||||
|
LOG.warn("aggregateInterface object was null when requested to cancel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregateInterface.cancelAction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
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.metadata.QBackendMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
@ -41,6 +42,12 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class CountAction
|
public class CountAction
|
||||||
{
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(CountAction.class);
|
||||||
|
|
||||||
|
private CountInterface countInterface;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -56,7 +63,7 @@ public class CountAction
|
|||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(countInput.getBackend());
|
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(countInput.getBackend());
|
||||||
|
|
||||||
CountInterface countInterface = qModule.getCountInterface();
|
countInterface = qModule.getCountInterface();
|
||||||
countInterface.setQueryStat(queryStat);
|
countInterface.setQueryStat(queryStat);
|
||||||
CountOutput countOutput = countInterface.execute(countInput);
|
CountOutput countOutput = countInterface.execute(countInput);
|
||||||
|
|
||||||
@ -64,4 +71,20 @@ public class CountAction
|
|||||||
|
|
||||||
return countOutput;
|
return countOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void cancel()
|
||||||
|
{
|
||||||
|
if(countInterface == null)
|
||||||
|
{
|
||||||
|
LOG.warn("countInterface object was null when requested to cancel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
countInterface.cancelAction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,7 @@ public class QueryAction
|
|||||||
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
|
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
|
||||||
|
|
||||||
private QueryInput queryInput;
|
private QueryInput queryInput;
|
||||||
|
private QueryInterface queryInterface;
|
||||||
private QPossibleValueTranslator qPossibleValueTranslator;
|
private QPossibleValueTranslator qPossibleValueTranslator;
|
||||||
|
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ public class QueryAction
|
|||||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
|
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
|
||||||
|
|
||||||
QueryInterface queryInterface = qModule.getQueryInterface();
|
queryInterface = qModule.getQueryInterface();
|
||||||
queryInterface.setQueryStat(queryStat);
|
queryInterface.setQueryStat(queryStat);
|
||||||
QueryOutput queryOutput = queryInterface.execute(queryInput);
|
QueryOutput queryOutput = queryInterface.execute(queryInput);
|
||||||
|
|
||||||
@ -339,4 +340,20 @@ public class QueryAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void cancel()
|
||||||
|
{
|
||||||
|
if(queryInterface == null)
|
||||||
|
{
|
||||||
|
LOG.warn("queryInterface object was null when requested to cancel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queryInterface.cancelAction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.tables.helpers;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** For actions that may want to set a timeout, and cancel themselves if they run
|
||||||
|
** too long - this class helps.
|
||||||
|
**
|
||||||
|
** Construct with the timeout (delay & timeUnit), and a runnable that takes care
|
||||||
|
** of doing the cancel (e.g., cancelling a JDBC statement).
|
||||||
|
**
|
||||||
|
** Call start() to make a future get scheduled (note, if delay was null or <= 0,
|
||||||
|
** then it doesn't get scheduled at all).
|
||||||
|
**
|
||||||
|
** Call cancel() if the action got far enough/completed, to cancel the future.
|
||||||
|
**
|
||||||
|
** You can check didTimeout (getDidTimeout()) to know if the timeout did occur.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ActionTimeoutHelper
|
||||||
|
{
|
||||||
|
private final Integer delay;
|
||||||
|
private final TimeUnit timeUnit;
|
||||||
|
private final Runnable runnable;
|
||||||
|
private ScheduledFuture<?> future;
|
||||||
|
|
||||||
|
private boolean didTimeout = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ActionTimeoutHelper(Integer delay, TimeUnit timeUnit, Runnable runnable)
|
||||||
|
{
|
||||||
|
this.delay = delay;
|
||||||
|
this.timeUnit = timeUnit;
|
||||||
|
this.runnable = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if(delay == null || delay <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
future = Executors.newSingleThreadScheduledExecutor().schedule(() ->
|
||||||
|
{
|
||||||
|
didTimeout = true;
|
||||||
|
runnable.run();
|
||||||
|
}, delay, timeUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void cancel()
|
||||||
|
{
|
||||||
|
if(future != null)
|
||||||
|
{
|
||||||
|
future.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for didTimeout
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getDidTimeout()
|
||||||
|
{
|
||||||
|
return didTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -40,6 +40,8 @@ public class AggregateInput extends AbstractTableActionInput
|
|||||||
private List<GroupBy> groupBys = new ArrayList<>();
|
private List<GroupBy> groupBys = new ArrayList<>();
|
||||||
private Integer limit;
|
private Integer limit;
|
||||||
|
|
||||||
|
private Integer timeoutSeconds;
|
||||||
|
|
||||||
private List<QueryJoin> queryJoins = null;
|
private List<QueryJoin> queryJoins = null;
|
||||||
|
|
||||||
|
|
||||||
@ -269,4 +271,35 @@ public class AggregateInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getTimeoutSeconds()
|
||||||
|
{
|
||||||
|
return (this.timeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setTimeoutSeconds(Integer timeoutSeconds)
|
||||||
|
{
|
||||||
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public AggregateInput withTimeoutSeconds(Integer timeoutSeconds)
|
||||||
|
{
|
||||||
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ public class CountInput extends AbstractTableActionInput
|
|||||||
{
|
{
|
||||||
private QQueryFilter filter;
|
private QQueryFilter filter;
|
||||||
|
|
||||||
|
private Integer timeoutSeconds;
|
||||||
|
|
||||||
private List<QueryJoin> queryJoins = null;
|
private List<QueryJoin> queryJoins = null;
|
||||||
private Boolean includeDistinctCount = false;
|
private Boolean includeDistinctCount = false;
|
||||||
|
|
||||||
@ -174,4 +176,35 @@ public class CountInput extends AbstractTableActionInput
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getTimeoutSeconds()
|
||||||
|
{
|
||||||
|
return (this.timeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setTimeoutSeconds(Integer timeoutSeconds)
|
||||||
|
{
|
||||||
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public CountInput withTimeoutSeconds(Integer timeoutSeconds)
|
||||||
|
{
|
||||||
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
|
|||||||
private QQueryFilter filter;
|
private QQueryFilter filter;
|
||||||
|
|
||||||
private RecordPipe recordPipe;
|
private RecordPipe recordPipe;
|
||||||
|
private Integer timeoutSeconds;
|
||||||
|
|
||||||
private boolean shouldTranslatePossibleValues = false;
|
private boolean shouldTranslatePossibleValues = false;
|
||||||
private boolean shouldGenerateDisplayValues = false;
|
private boolean shouldGenerateDisplayValues = false;
|
||||||
@ -537,4 +538,35 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getTimeoutSeconds()
|
||||||
|
{
|
||||||
|
return (this.timeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setTimeoutSeconds(Integer timeoutSeconds)
|
||||||
|
{
|
||||||
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for timeoutSeconds
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryInput withTimeoutSeconds(Integer timeoutSeconds)
|
||||||
|
{
|
||||||
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ 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.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@ -91,6 +92,9 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
|
|
||||||
protected QueryStat queryStat;
|
protected QueryStat queryStat;
|
||||||
|
|
||||||
|
protected PreparedStatement statement;
|
||||||
|
protected boolean isCancelled = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -1094,4 +1098,28 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
|||||||
this.queryStat = queryStat;
|
this.queryStat = queryStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected void doCancelQuery()
|
||||||
|
{
|
||||||
|
isCancelled = true;
|
||||||
|
if(statement == null)
|
||||||
|
{
|
||||||
|
LOG.warn("Statement was null when requested to cancel query");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
statement.cancel();
|
||||||
|
}
|
||||||
|
catch(SQLException e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error trying to cancel query (statement)", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,11 @@ import java.sql.Connection;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ActionTimeoutHelper;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
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.AggregateInput;
|
||||||
@ -53,6 +56,7 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(RDBMSAggregateAction.class);
|
private static final QLogger LOG = QLogger.getLogger(RDBMSAggregateAction.class);
|
||||||
|
|
||||||
|
private ActionTimeoutHelper actionTimeoutHelper;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -102,8 +106,21 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
|
|
||||||
try(Connection connection = getConnection(aggregateInput))
|
try(Connection connection = getConnection(aggregateInput))
|
||||||
{
|
{
|
||||||
QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
|
statement = connection.prepareStatement(sql);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// set up & start an actionTimeoutHelper (note, internally it'll deal with the time being null or negative as meaning not to timeout) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper = new ActionTimeoutHelper(aggregateInput.getTimeoutSeconds(), TimeUnit.SECONDS, new StatementTimeoutCanceller(statement, sql));
|
||||||
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
|
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
|
||||||
while(resultSet.next())
|
while(resultSet.next())
|
||||||
{
|
{
|
||||||
setQueryStatFirstResultTime();
|
setQueryStatFirstResultTime();
|
||||||
@ -156,9 +173,30 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
|
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||||
|
{
|
||||||
|
setQueryStatFirstResultTime();
|
||||||
|
throw (new QUserFacingException("Aggregate query timed out."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isCancelled)
|
||||||
|
{
|
||||||
|
throw (new QUserFacingException("Aggregate query was cancelled."));
|
||||||
|
}
|
||||||
|
|
||||||
LOG.warn("Error executing aggregate", e);
|
LOG.warn("Error executing aggregate", e);
|
||||||
throw new QException("Error executing aggregate", e);
|
throw new QException("Error executing aggregate", e);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if(actionTimeoutHelper != null)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// make sure the timeout got cancelled //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -199,4 +237,15 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
|
|||||||
return (StringUtils.join(",", columns));
|
return (StringUtils.join(",", columns));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void cancelAction()
|
||||||
|
{
|
||||||
|
doCancelQuery();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,11 @@ import java.sql.Connection;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ActionTimeoutHelper;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
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;
|
||||||
@ -46,6 +49,8 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
|||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(RDBMSCountAction.class);
|
private static final QLogger LOG = QLogger.getLogger(RDBMSCountAction.class);
|
||||||
|
|
||||||
|
private ActionTimeoutHelper actionTimeoutHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -84,8 +89,21 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
|||||||
{
|
{
|
||||||
long mark = System.currentTimeMillis();
|
long mark = System.currentTimeMillis();
|
||||||
|
|
||||||
QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) ->
|
statement = connection.prepareStatement(sql);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// set up & start an actionTimeoutHelper (note, internally it'll deal with the time being null or negative as meaning not to timeout) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper = new ActionTimeoutHelper(countInput.getTimeoutSeconds(), TimeUnit.SECONDS, new StatementTimeoutCanceller(statement, sql));
|
||||||
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
|
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
|
||||||
if(resultSet.next())
|
if(resultSet.next())
|
||||||
{
|
{
|
||||||
rs.setCount(resultSet.getInt("record_count"));
|
rs.setCount(resultSet.getInt("record_count"));
|
||||||
@ -107,9 +125,41 @@ public class RDBMSCountAction extends AbstractRDBMSAction implements CountInterf
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
|
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||||
|
{
|
||||||
|
setQueryStatFirstResultTime();
|
||||||
|
throw (new QUserFacingException("Count timed out."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isCancelled)
|
||||||
|
{
|
||||||
|
throw (new QUserFacingException("Count was cancelled."));
|
||||||
|
}
|
||||||
|
|
||||||
LOG.warn("Error executing count", e);
|
LOG.warn("Error executing count", e);
|
||||||
throw new QException("Error executing count", e);
|
throw new QException("Error executing count", e);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if(actionTimeoutHelper != null)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// make sure the timeout got cancelled //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void cancelAction()
|
||||||
|
{
|
||||||
|
doCancelQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,12 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ActionTimeoutHelper;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
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;
|
||||||
@ -60,6 +63,8 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(RDBMSQueryAction.class);
|
private static final QLogger LOG = QLogger.getLogger(RDBMSQueryAction.class);
|
||||||
|
|
||||||
|
private ActionTimeoutHelper actionTimeoutHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -136,14 +141,29 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////
|
||||||
|
// create a statement from the SQL //
|
||||||
|
/////////////////////////////////////
|
||||||
|
statement = createStatement(connection, sql.toString(), queryInput);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// set up & start an actionTimeoutHelper (note, internally it'll deal with the time being null or negative as meaning not to timeout) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper = new ActionTimeoutHelper(queryInput.getTimeoutSeconds(), TimeUnit.SECONDS, new StatementTimeoutCanceller(statement, sql));
|
||||||
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
// execute the query - iterate over results //
|
// execute the query - iterate over results //
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||||
|
|
||||||
PreparedStatement statement = createStatement(connection, sql.toString(), queryInput);
|
|
||||||
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
|
||||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||||
while(resultSet.next())
|
while(resultSet.next())
|
||||||
{
|
{
|
||||||
@ -201,6 +221,14 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
if(actionTimeoutHelper != null)
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// make sure the timeout got cancelled //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
if(needToCloseConnection)
|
if(needToCloseConnection)
|
||||||
{
|
{
|
||||||
connection.close();
|
connection.close();
|
||||||
@ -209,6 +237,17 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
|
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||||
|
{
|
||||||
|
setQueryStatFirstResultTime();
|
||||||
|
throw (new QUserFacingException("Query timed out."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isCancelled)
|
||||||
|
{
|
||||||
|
throw (new QUserFacingException("Query was cancelled."));
|
||||||
|
}
|
||||||
|
|
||||||
LOG.warn("Error executing query", e);
|
LOG.warn("Error executing query", e);
|
||||||
throw new QException("Error executing query", e);
|
throw new QException("Error executing query", e);
|
||||||
}
|
}
|
||||||
@ -282,20 +321,6 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
private boolean filterOutHeavyFieldsIfNeeded(QFieldMetaData field, boolean shouldFetchHeavyFields)
|
|
||||||
{
|
|
||||||
if(!shouldFetchHeavyFields && field.getIsHeavy())
|
|
||||||
{
|
|
||||||
return (false);
|
|
||||||
}
|
|
||||||
return (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** if we're not fetching heavy fields, instead just get their length. this
|
** if we're not fetching heavy fields, instead just get their length. this
|
||||||
** method wraps the field 'sql name' (e.g., column_name or table_name.column_name)
|
** method wraps the field 'sql name' (e.g., column_name or table_name.column_name)
|
||||||
@ -338,4 +363,14 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
return (statement);
|
return (statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void cancelAction()
|
||||||
|
{
|
||||||
|
doCancelQuery();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Helper to cancel statements that timeout.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class StatementTimeoutCanceller implements Runnable
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(StatementTimeoutCanceller.class);
|
||||||
|
|
||||||
|
private final Statement statement;
|
||||||
|
private final String sql;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public StatementTimeoutCanceller(Statement statement, CharSequence sql)
|
||||||
|
{
|
||||||
|
this.statement = statement;
|
||||||
|
this.sql = sql.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
statement.cancel();
|
||||||
|
LOG.info("Cancelled timed out statement", logPair("sql", sql));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error trying to cancel statement after timeout", e, logPair("sql", sql));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw (new QRuntimeException("Statement timed out and was cancelled."));
|
||||||
|
}
|
||||||
|
}
|
@ -162,6 +162,9 @@ public class QJavalinImplementation
|
|||||||
private static final long MILLIS_BETWEEN_HOT_SWAPS = 2500;
|
private static final long MILLIS_BETWEEN_HOT_SWAPS = 2500;
|
||||||
public static final long SLOW_LOG_THRESHOLD_MS = 1000;
|
public static final long SLOW_LOG_THRESHOLD_MS = 1000;
|
||||||
|
|
||||||
|
private static final Integer DEFAULT_COUNT_TIMEOUT_SECONDS = 60;
|
||||||
|
private static final Integer DEFAULT_QUERY_TIMEOUT_SECONDS = 60;
|
||||||
|
|
||||||
private static int DEFAULT_PORT = 8001;
|
private static int DEFAULT_PORT = 8001;
|
||||||
|
|
||||||
private static Javalin service;
|
private static Javalin service;
|
||||||
@ -1075,6 +1078,7 @@ public class QJavalinImplementation
|
|||||||
countInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
countInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"));
|
||||||
|
|
||||||
@ -1131,6 +1135,7 @@ public class QJavalinImplementation
|
|||||||
queryInput.setTableName(table);
|
queryInput.setTableName(table);
|
||||||
queryInput.setShouldGenerateDisplayValues(true);
|
queryInput.setShouldGenerateDisplayValues(true);
|
||||||
queryInput.setShouldTranslatePossibleValues(true);
|
queryInput.setShouldTranslatePossibleValues(true);
|
||||||
|
queryInput.setTimeoutSeconds(DEFAULT_QUERY_TIMEOUT_SECONDS);
|
||||||
|
|
||||||
PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ);
|
PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user