This is an automated email from the ASF dual-hosted git repository. abulatski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/master by this push: new 7d8eef1 CAY-2590 Add method to set query timeout to queries new 731794d Merge PR #396 7d8eef1 is described below commit 7d8eef1432505f27fc4a96620d820010ca29fcc9 Author: Arseni Bulatski <ancars...@gmail.com> AuthorDate: Thu Jul 4 15:56:00 2019 +0300 CAY-2590 Add method to set query timeout to queries --- RELEASE-NOTES.txt | 1 + .../apache/cayenne/lifecycle/id/StringIdQuery.java | 5 + .../org/apache/cayenne/access/DataDomainQuery.java | 5 + .../cayenne/access/ObjectsFromDataRowsQuery.java | 5 + .../apache/cayenne/access/jdbc/EJBQLAction.java | 8 +- .../cayenne/access/jdbc/ProcedureAction.java | 9 +- .../cayenne/access/jdbc/SQLTemplateAction.java | 29 +++--- .../apache/cayenne/access/jdbc/SelectAction.java | 15 ++- .../apache/cayenne/query/BaseQueryMetadata.java | 20 +++- .../org/apache/cayenne/query/ColumnSelect.java | 9 ++ .../apache/cayenne/query/DefaultQueryMetadata.java | 5 + .../java/org/apache/cayenne/query/EJBQLQuery.java | 29 ++++-- .../org/apache/cayenne/query/FluentSelect.java | 7 ++ .../org/apache/cayenne/query/MappedSelect.java | 30 +++++- .../org/apache/cayenne/query/ObjectSelect.java | 11 ++ .../org/apache/cayenne/query/ProcedureQuery.java | 28 +++-- .../org/apache/cayenne/query/QueryMetadata.java | 8 ++ .../apache/cayenne/query/QueryMetadataProxy.java | 5 + .../java/org/apache/cayenne/query/SQLExec.java | 22 ++-- .../java/org/apache/cayenne/query/SQLSelect.java | 19 ++++ .../java/org/apache/cayenne/query/SQLTemplate.java | 32 ++++-- .../java/org/apache/cayenne/query/SelectQuery.java | 16 +-- .../apache/cayenne/query/MockQueryMetadata.java | 5 + .../org/apache/cayenne/query/ObjectSelectTest.java | 21 ++-- .../org/apache/cayenne/query/QueryTimeoutIT.java | 113 +++++++++++++++++++++ 25 files changed, 384 insertions(+), 73 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 3aa2fa8..3d648ac 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -37,6 +37,7 @@ CAY-2570 Use MySQL adapter for latest versions of MariaDB CAY-2579 Review and possibly relax usage of readonly flag of ObjRelationship CAY-2585 Rename scalarQuery and params methods in SQLSelect CAY-2589 - Allow optionally using a local query cache that is separate from the shared query cache. +CAY-2590 Add method to set query timeout to queries Bug Fixes: diff --git a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java index 4516dbd..b163250 100644 --- a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java +++ b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java @@ -226,6 +226,11 @@ public class StringIdQuery implements Query { } @Override + public int getQueryTimeout() { + return QueryMetadata.QUERY_TIMEOUT_DEFAULT; + } + + @Override public boolean isSuppressingDistinct() { return false; } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java index 647c88d..353c925 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java @@ -146,6 +146,11 @@ class DataDomainQuery implements Query, QueryMetadata { } @Override + public int getQueryTimeout() { + return QueryMetadata.QUERY_TIMEOUT_DEFAULT; + } + + @Override public boolean isSuppressingDistinct() { return false; } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java index b80b832..d4ed479 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java @@ -146,6 +146,11 @@ class ObjectsFromDataRowsQuery implements Query, QueryMetadata { } @Override + public int getQueryTimeout() { + return QUERY_TIMEOUT_DEFAULT; + } + + @Override public boolean isSuppressingDistinct() { return false; } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/EJBQLAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/EJBQLAction.java index 58a86dd..c4bfb16 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/EJBQLAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/EJBQLAction.java @@ -19,7 +19,6 @@ package org.apache.cayenne.access.jdbc; import java.sql.Connection; -import java.sql.SQLException; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; @@ -52,7 +51,7 @@ public class EJBQLAction extends BaseSQLAction { } @Override - public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception { + public void performAction(Connection connection, OperationObserver observer) throws Exception { EJBQLCompiledExpression compiledExpression = query.getExpression(dataNode.getEntityResolver()); final EJBQLTranslatorFactory translatorFactory = dataNode.getAdapter().getEjbqlTranslatorFactory(); final EJBQLTranslationContext context = new EJBQLTranslationContext(dataNode.getEntityResolver(), query, @@ -95,6 +94,11 @@ public class EJBQLAction extends BaseSQLAction { sqlQuery.setStatementFetchSize(md.getStatementFetchSize()); } + int queryTimeout = md.getQueryTimeout(); + if(queryTimeout != QueryMetadata.QUERY_TIMEOUT_DEFAULT) { + sqlQuery.setQueryTimeout(queryTimeout); + } + actionFactory.sqlAction(sqlQuery).performAction(connection, observer); } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java index b2b2200..7af5142 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java @@ -35,6 +35,7 @@ import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.map.Procedure; import org.apache.cayenne.map.ProcedureParameter; import org.apache.cayenne.query.ProcedureQuery; +import org.apache.cayenne.query.QueryMetadata; /** * A SQLAction that runs a stored procedure. Note that ProcedureAction has @@ -220,9 +221,15 @@ public class ProcedureAction extends BaseSQLAction { * @throws Exception */ protected void initStatement(CallableStatement statement) throws Exception { - int statementFetchSize = query.getMetaData(dataNode.getEntityResolver()).getStatementFetchSize(); + QueryMetadata queryMetadata = query.getMetaData(dataNode.getEntityResolver()); + int statementFetchSize = queryMetadata.getStatementFetchSize(); if (statementFetchSize != 0) { statement.setFetchSize(statementFetchSize); } + + int queryTimeout = queryMetadata.getQueryTimeout(); + if(queryTimeout != QueryMetadata.QUERY_TIMEOUT_DEFAULT) { + statement.setQueryTimeout(queryTimeout); + } } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java index 90337e1..c3029ab 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java @@ -19,6 +19,18 @@ package org.apache.cayenne.access.jdbc; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.ResultIterator; import org.apache.cayenne.access.DataNode; @@ -39,18 +51,6 @@ import org.apache.cayenne.query.SQLAction; import org.apache.cayenne.query.SQLTemplate; import org.apache.cayenne.util.Util; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - /** * Implements a strategy for execution of SQLTemplates. * @@ -403,6 +403,11 @@ public class SQLTemplateAction implements SQLAction { if (queryMetadata.getStatementFetchSize() != 0) { preparedStatement.setFetchSize(queryMetadata.getStatementFetchSize()); } + + int queryTimeout = queryMetadata.getQueryTimeout(); + if(queryTimeout != QueryMetadata.QUERY_TIMEOUT_DEFAULT) { + preparedStatement.setQueryTimeout(queryTimeout); + } } /** diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java index 085cc3e..4f640a9 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java @@ -19,6 +19,11 @@ package org.apache.cayenne.access.jdbc; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.List; + import org.apache.cayenne.ResultIterator; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; @@ -32,11 +37,6 @@ import org.apache.cayenne.query.PrefetchTreeNode; import org.apache.cayenne.query.QueryMetadata; import org.apache.cayenne.query.Select; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.List; - /** * A SQLAction that handles SelectQuery execution. * @@ -97,6 +97,11 @@ public class SelectAction extends BaseSQLAction { statement.setFetchSize(fetchSize); } + int queryTimeout = queryMetadata.getQueryTimeout(); + if(queryTimeout != QueryMetadata.QUERY_TIMEOUT_DEFAULT) { + statement.setQueryTimeout(queryTimeout); + } + ResultSet rs; // need to run in try-catch block to close statement properly if diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java index ef31715..3b1142e 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java @@ -27,15 +27,12 @@ import java.util.Map; import java.util.StringTokenizer; import org.apache.cayenne.Persistent; -import org.apache.cayenne.configuration.ConfigurationNodeVisitor; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.EntityResolver; import org.apache.cayenne.map.ObjEntity; import org.apache.cayenne.map.Procedure; import org.apache.cayenne.reflect.ClassDescriptor; -import org.apache.cayenne.util.XMLEncoder; -import org.apache.cayenne.util.XMLSerializable; /** * Default mutable implementation of {@link QueryMetadata}. @@ -50,6 +47,7 @@ class BaseQueryMetadata implements QueryMetadata, Serializable { int fetchOffset = QueryMetadata.FETCH_OFFSET_DEFAULT; int statementFetchSize = QueryMetadata.FETCH_OFFSET_DEFAULT; + int queryTimeout = QueryMetadata.QUERY_TIMEOUT_DEFAULT; int pageSize = QueryMetadata.PAGE_SIZE_DEFAULT; boolean fetchingDataRows = QueryMetadata.FETCHING_DATA_ROWS_DEFAULT; @@ -386,6 +384,22 @@ class BaseQueryMetadata implements QueryMetadata, Serializable { } /** + * Sets query timeout(0 means no limit, -1 if doesn't set) + * @since 4.2 + */ + void setQueryTimeout(int queryTimeout) { + this.queryTimeout = queryTimeout; + } + + /** + * @return query timeout + * @since 4.2 + */ + public int getQueryTimeout() { + return queryTimeout; + } + + /** * Adds a joint prefetch. * * @since 1.2 diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java index fb380e6..b82c8c8 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java @@ -299,6 +299,15 @@ public class ColumnSelect<T> extends FluentSelect<T> { return this; } + /** + * Sets query timeout of PreparedStatement generated for this query. + * @see Statement#setQueryTimeout(int) + */ + public ColumnSelect<T> queryTimeout(int timeout) { + this.metaData.setQueryTimeout(timeout); + return this; + } + public ColumnSelect<T> cacheStrategy(QueryCacheStrategy strategy) { setCacheStrategy(strategy); setCacheGroup(null); diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java index 05a69ca..af89c59 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java @@ -147,6 +147,11 @@ class DefaultQueryMetadata implements QueryMetadata { return QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT; } + @Override + public int getQueryTimeout() { + return QueryMetadata.QUERY_TIMEOUT_DEFAULT; + } + /** * @since 4.0 */ diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java index 67ff9e0..5bf9506 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java @@ -18,19 +18,16 @@ ****************************************************************/ package org.apache.cayenne.query; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.configuration.ConfigurationNodeVisitor; import org.apache.cayenne.ejbql.EJBQLCompiledExpression; import org.apache.cayenne.ejbql.EJBQLException; import org.apache.cayenne.ejbql.EJBQLParserFactory; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.util.XMLEncoder; -import org.apache.cayenne.util.XMLSerializable; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; /** * An EJBQL query representation in Cayenne. @@ -221,6 +218,20 @@ public class EJBQLQuery extends CacheableQuery { public int getStatementFetchSize() { return metadata.getStatementFetchSize(); } - - + + /** + * Sets query timeout. + * @since 4.2 + */ + public void setQueryTimeout(int queryTimeout) { + metadata.setQueryTimeout(queryTimeout); + } + + /** + * @return query timeout + * @since 4.2 + */ + public int getQueryTimeout() { + return metadata.getQueryTimeout(); + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java index 2ce013c..42a055b 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java @@ -84,6 +84,13 @@ public abstract class FluentSelect<T> extends AbstractQuery implements Select<T> return getBaseMetaData().getStatementFetchSize(); } + /** + * @since 4.2 + */ + public int getQueryTimeout() { + return getBaseMetaData().getQueryTimeout(); + } + public int getPageSize() { return getBaseMetaData().getPageSize(); } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/MappedSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/MappedSelect.java index e289064..be7a378 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/MappedSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/MappedSelect.java @@ -18,6 +18,10 @@ ****************************************************************/ package org.apache.cayenne.query; +import java.sql.Statement; +import java.util.List; +import java.util.Map; + import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.ResultBatchIterator; @@ -26,10 +30,6 @@ import org.apache.cayenne.ResultIteratorCallback; import org.apache.cayenne.map.EntityResolver; import org.apache.cayenne.map.QueryDescriptor; -import java.sql.Statement; -import java.util.List; -import java.util.Map; - /** * A query that represents a named parameterized selecting query stored in the mapping. The * actual query is resolved during execution. @@ -62,6 +62,7 @@ public class MappedSelect<T> extends AbstractMappedQuery implements Select<T> { protected Integer fetchLimit; protected Integer fetchOffset; protected Integer statementFetchSize; + protected Integer queryTimeout; protected Integer pageSize; protected boolean forceNoCache; @@ -107,6 +108,18 @@ public class MappedSelect<T> extends AbstractMappedQuery implements Select<T> { } /** + * Sets query timeout for the PreparedStatement generated for this query. + * + * @see Statement#setQueryTimeout(int) + * @since 4.2 + */ + public MappedSelect<T> queryTimeout(int timeout) { + this.queryTimeout = timeout; + this.replacementQuery = null; + return this; + } + + /** * Resets query page size. A non-negative page size enables query result * pagination that saves memory and processing time for large lists if only * parts of the result are ever going to be accessed. @@ -214,6 +227,9 @@ public class MappedSelect<T> extends AbstractMappedQuery implements Select<T> { if (statementFetchSize != null) { sqlTemplate.setStatementFetchSize(statementFetchSize); } + if(queryTimeout != null) { + sqlTemplate.setQueryTimeout(queryTimeout); + } if (pageSize != null) { sqlTemplate.setPageSize(pageSize); } @@ -232,6 +248,9 @@ public class MappedSelect<T> extends AbstractMappedQuery implements Select<T> { if (statementFetchSize != null) { ejbqlQuery.setStatementFetchSize(statementFetchSize); } + if(queryTimeout != null) { + ejbqlQuery.setQueryTimeout(queryTimeout); + } if (pageSize != null) { ejbqlQuery.setPageSize(pageSize); } @@ -250,6 +269,9 @@ public class MappedSelect<T> extends AbstractMappedQuery implements Select<T> { if (statementFetchSize != null) { procedureQuery.setStatementFetchSize(statementFetchSize); } + if(queryTimeout != null) { + procedureQuery.setQueryTimeout(queryTimeout); + } if (pageSize != null) { procedureQuery.setPageSize(pageSize); } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java index 66e4762..1ce27ac 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java @@ -441,6 +441,17 @@ public class ObjectSelect<T> extends FluentSelect<T> { return this; } + /** + * Sets query timeout for PreparedStatement generated for this query. + * + * @see Statement#setQueryTimeout(int) + * @since 4.2 + */ + public ObjectSelect<T> queryTimeout(int timeout) { + this.metaData.setQueryTimeout(timeout); + return this; + } + public ObjectSelect<T> cacheStrategy(QueryCacheStrategy strategy) { setCacheStrategy(strategy); setCacheGroup(null); diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java index 98e302f..7e77d6b 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java @@ -19,14 +19,6 @@ package org.apache.cayenne.query; -import org.apache.cayenne.access.jdbc.ColumnDescriptor; -import org.apache.cayenne.configuration.ConfigurationNodeVisitor; -import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.map.Procedure; -import org.apache.cayenne.map.QueryDescriptor; -import org.apache.cayenne.util.XMLEncoder; -import org.apache.cayenne.util.XMLSerializable; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -34,6 +26,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.map.Procedure; + /** * A query based on Procedure. Can be used as a select query, or as a query of an * arbitrary complexity, performing data modification, selecting data (possibly with @@ -430,4 +426,20 @@ public class ProcedureQuery extends AbstractQuery implements ParameterizedQuery public int getStatementFetchSize() { return metaData.getStatementFetchSize(); } + + /** + * @return query timeout + * @since 4.2 + */ + public int getQueryTimeout() { + return metaData.getQueryTimeout(); + } + + /** + * Set's query timeout + * @since 4.2 + */ + public void setQueryTimeout(int timeout) { + metaData.setQueryTimeout(timeout); + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java index 40e1793..396cac1 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java @@ -63,6 +63,8 @@ public interface QueryMetadata { */ int FETCH_OFFSET_DEFAULT = 0; + int QUERY_TIMEOUT_DEFAULT = -1; + /** * Defines the name of the property for the query {@link #getPageSize() page size}. */ @@ -245,6 +247,12 @@ public interface QueryMetadata { int getStatementFetchSize(); /** + * @return query timeout + * @since 4.2 + */ + int getQueryTimeout(); + + /** * @since 4.0 */ boolean isSuppressingDistinct(); diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java index e1cef43..b39b177 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java @@ -133,6 +133,11 @@ public class QueryMetadataProxy implements QueryMetadata { } @Override + public int getQueryTimeout() { + return mdDelegate.getQueryTimeout(); + } + + @Override public boolean isSuppressingDistinct() { return mdDelegate.isSuppressingDistinct(); } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLExec.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLExec.java index ee42f67..00c55b2 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLExec.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLExec.java @@ -18,6 +18,12 @@ ****************************************************************/ package org.apache.cayenne.query; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.QueryResponse; @@ -26,12 +32,6 @@ import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.EntityResolver; import org.apache.cayenne.util.QueryResultBuilder; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** * A generic query based on raw SQL and featuring fluent API. While * {@link SQLExec} can be used to select data (see {@link #execute(ObjectContext)} @@ -65,6 +65,7 @@ public class SQLExec extends IndirectQuery { protected Map<String, Object> params; protected List<Object> positionalParams; protected boolean returnGeneratedKeys; + protected int queryTimeout; public SQLExec(String sql) { this.sqlBuffer = sql != null ? new StringBuilder(sql) : new StringBuilder(); @@ -220,6 +221,14 @@ public class SQLExec extends IndirectQuery { return this; } + /** + * @since 4.2 + */ + public SQLExec queryTimeout(int queryTimeout) { + this.queryTimeout = queryTimeout; + return this; + } + @Override protected Query createReplacementQuery(EntityResolver resolver) { @@ -242,6 +251,7 @@ public class SQLExec extends IndirectQuery { template.setDefaultTemplate(getSql()); template.setFetchingDataRows(true); // in case result set will be returned template.setReturnGeneratedKeys(returnGeneratedKeys); + template.setQueryTimeout(queryTimeout); if (positionalParams != null) { template.setParamsList(positionalParams); diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java index 96af190..b2f426f 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java @@ -255,6 +255,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> { protected int offset; protected int pageSize; protected int statementFetchSize; + protected int queryTimeout; protected PrefetchTreeNode prefetches; public SQLSelect(String sql) { @@ -440,6 +441,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> { template.setFetchOffset(offset); template.setPageSize(pageSize); template.setStatementFetchSize(statementFetchSize); + template.setQueryTimeout(queryTimeout); template.setUseScalar(useScalar); return template; @@ -623,12 +625,29 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> { } /** + * Sets query timeout + * @since 4.2 + */ + public SQLSelect<T> queryTimeout(int queryTimeout) { + if(this.queryTimeout != queryTimeout) { + this.queryTimeout = queryTimeout; + this.replacementQuery = null; + } + + return this; + } + + /** * @return JBDC statement's fetch size */ public int getStatementFetchSize() { return statementFetchSize; } + public int getQueryTimeout() { + return queryTimeout; + } + /** * Merges a prefetch path with specified semantics into the query prefetch tree. * @param path Path expression diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java index 3f27484..b2d5e9a 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java @@ -19,14 +19,6 @@ package org.apache.cayenne.query; -import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.access.QueryEngine; -import org.apache.cayenne.map.DataMap; -import org.apache.cayenne.map.DbEntity; -import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.map.ObjEntity; -import org.apache.cayenne.map.SQLResult; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -38,6 +30,14 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Stream; +import org.apache.cayenne.CayenneRuntimeException; +import org.apache.cayenne.access.QueryEngine; +import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.map.ObjEntity; +import org.apache.cayenne.map.SQLResult; + /** * A query that executes unchanged (except for template preprocessing) "raw" SQL * specified by the user. <h3>Template Script</h3> @@ -625,6 +625,22 @@ public class SQLTemplate extends AbstractQuery implements ParameterizedQuery { } /** + * Sets query timeout. + * @since 4.2 + */ + public void setQueryTimeout(int queryTimeout) { + metaData.setQueryTimeout(queryTimeout); + } + + /** + * @return query timeout + * @since 4.2 + */ + public int getQueryTimeout() { + return metaData.getQueryTimeout(); + } + + /** * Returns a name of the DataNode to use with this SQLTemplate. This * information will be used during query execution if no other routing * information is provided such as entity name or class, etc. diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java index 51f225e..55c5a68 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java @@ -19,25 +19,25 @@ package org.apache.cayenne.query; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import org.apache.cayenne.DataRow; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.ResultBatchIterator; import org.apache.cayenne.ResultIterator; import org.apache.cayenne.ResultIteratorCallback; -import org.apache.cayenne.exp.property.BaseProperty; import org.apache.cayenne.exp.Expression; import org.apache.cayenne.exp.ExpressionFactory; +import org.apache.cayenne.exp.property.BaseProperty; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.EntityResolver; import org.apache.cayenne.map.ObjEntity; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - /** * A query that selects persistent objects of a certain type or "raw data" (aka * DataRows). Supports expression qualifier, multiple orderings and a number of diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java b/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java index 6d2e533..aa695a4 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java @@ -112,6 +112,11 @@ public class MockQueryMetadata implements QueryMetadata { } @Override + public int getQueryTimeout() { + return QueryMetadata.QUERY_TIMEOUT_DEFAULT; + } + + @Override public boolean isSuppressingDistinct() { return false; } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectTest.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectTest.java index 21dad3e..1179ded 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectTest.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectTest.java @@ -18,13 +18,6 @@ ****************************************************************/ package org.apache.cayenne.query; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -35,6 +28,8 @@ import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.testdo.testmap.Artist; import org.junit.Test; +import static org.junit.Assert.*; + public class ObjectSelectTest { @Test @@ -428,4 +423,16 @@ public class ObjectSelectTest { assertNull(q.getCacheGroup()); } + @Test + public void testQueryTimeout() { + ObjectSelect<Artist> query = ObjectSelect.query(Artist.class); + assertEquals(-1, query.getQueryTimeout()); + + query.queryTimeout(10); + assertEquals(10, query.getQueryTimeout()); + + query.queryTimeout(1).queryTimeout(2); + assertEquals(2, query.getQueryTimeout()); + } + } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/QueryTimeoutIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/QueryTimeoutIT.java new file mode 100644 index 0000000..d84199f --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/QueryTimeoutIT.java @@ -0,0 +1,113 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.query; + +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.testdo.testmap.Artist; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) +public class QueryTimeoutIT extends ServerCase { + + @Inject + private ObjectContext context; + + @Test + public void testObjectSelect() { + ObjectSelect<Artist> objectSelect = ObjectSelect.query(Artist.class) + .queryTimeout(10); + assertEquals(10, objectSelect + .getMetaData(context.getEntityResolver()) + .getQueryTimeout()); + objectSelect.select(context); + } + + @Test + public void testSQLTemplate() { + SQLTemplate sqlTemplate = new SQLTemplate(); + sqlTemplate.setDefaultTemplate("SELECT * FROM ARTIST"); + sqlTemplate.setQueryTimeout(10); + assertEquals(10, sqlTemplate + .getMetaData(context.getEntityResolver()) + .getQueryTimeout()); + context.performQuery(sqlTemplate); + } + + @Test + public void testColumnSelect() { + ColumnSelect<String> columnSelect = ObjectSelect + .columnQuery(Artist.class, Artist.ARTIST_NAME) + .queryTimeout(10); + assertEquals(10, columnSelect + .getMetaData(context.getEntityResolver()) + .getQueryTimeout()); + context.performQuery(columnSelect); + } + + @Test + public void testEjbql() { + EJBQLQuery ejbqlQuery = new EJBQLQuery("select a from Artist a"); + ejbqlQuery.setQueryTimeout(10); + assertEquals(10, ejbqlQuery + .getMetaData(context.getEntityResolver()) + .getQueryTimeout()); + context.performQuery(ejbqlQuery); + } + + @Test + public void testSqlSelect() { + SQLSelect<Artist> sqlSelect = SQLSelect + .query(Artist.class, "SELECT * FROM ARTIST") + .queryTimeout(10); + assertEquals(10, sqlSelect + .getMetaData(context.getEntityResolver()) + .getQueryTimeout()); + context.performQuery(sqlSelect); + } + + @Test + public void testSqlExec() { + SQLExec sqlExec = SQLExec + .query("SELECT * FROM ARTIST") + .queryTimeout(10); + assertEquals(10, sqlExec + .getMetaData(context.getEntityResolver()) + .getQueryTimeout()); + context.performQuery(sqlExec); + } + + @Test + public void testMappedSelect() { + MappedSelect<Artist> mappedSelect = MappedSelect + .query("SelectTestUpper", Artist.class) + .queryTimeout(10); + Query replacementQuery = mappedSelect.createReplacementQuery(context.getEntityResolver()); + assertEquals(10, replacementQuery + .getMetaData(context.getEntityResolver()) + .getQueryTimeout()); + context.performQuery(replacementQuery); + } +}