This is an automated email from the ASF dual-hosted git repository.
vladimirsitnikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push:
new 21fe666 [CALCITE-2301] JDBC adapter: use query timeout from the
top-level statement
21fe666 is described below
commit 21fe666e9634ab9a752c7cc328924b68f6143afe
Author: Vladimir Sitnikov <[email protected]>
AuthorDate: Tue Jan 8 22:48:01 2019 +0300
[CALCITE-2301] JDBC adapter: use query timeout from the top-level statement
fixes #995
---
.../main/java/org/apache/calcite/DataContext.java | 4 ++
.../adapter/jdbc/JdbcToEnumerableConverter.java | 6 ++-
.../apache/calcite/jdbc/CalciteConnectionImpl.java | 5 +++
.../apache/calcite/runtime/CalciteResource.java | 3 ++
.../calcite/runtime/ResultSetEnumerable.java | 48 +++++++++++++++++++---
.../org/apache/calcite/util/BuiltInMethod.java | 2 +
.../calcite/runtime/CalciteResource.properties | 1 +
7 files changed, 62 insertions(+), 7 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/DataContext.java
b/core/src/main/java/org/apache/calcite/DataContext.java
index c4cafe6..32fa01e 100644
--- a/core/src/main/java/org/apache/calcite/DataContext.java
+++ b/core/src/main/java/org/apache/calcite/DataContext.java
@@ -85,6 +85,10 @@ public interface DataContext {
* frequently and cease execution (e.g. by returning end of data). */
CANCEL_FLAG("cancelFlag", AtomicBoolean.class),
+ /** Query timeout in milliseconds.
+ * When no timeout is set, the value is 0 or not present. */
+ TIMEOUT("timeout", Long.class),
+
/** Advisor that suggests completion hints for SQL statements. */
SQL_ADVISOR("sqlAdvisor", SqlAdvisor.class),
diff --git
a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java
index a43d2b1..f24f6f3 100644
---
a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java
+++
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcToEnumerableConverter.java
@@ -190,7 +190,11 @@ public class JdbcToEnumerableConverter
sql_,
rowBuilderFactory_));
}
-
+ builder0.add(
+ Expressions.statement(
+ Expressions.call(enumerable,
+ BuiltInMethod.RESULT_SET_ENUMERABLE_SET_TIMEOUT.method,
+ DataContext.ROOT)));
builder0.add(
Expressions.return_(null, enumerable));
return implementor.result(physType, builder0.toBlock());
diff --git
a/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java
b/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java
index 7df8c3e..83ec60b 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java
@@ -305,6 +305,11 @@ abstract class CalciteConnectionImpl
throw new RuntimeException(e);
}
map.put(DataContext.Variable.CANCEL_FLAG.camelName, cancelFlag);
+ int queryTimeout = statement.getQueryTimeout();
+ // Avoid overflow
+ if (queryTimeout > 0 && queryTimeout < Integer.MAX_VALUE / 1000) {
+ map.put(DataContext.Variable.TIMEOUT.camelName, queryTimeout * 1000L);
+ }
final DataContext dataContext = createDataContext(map,
signature.rootSchema);
return signature.enumerable(dataContext);
}
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index ee0953d..4142f67 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -847,6 +847,9 @@ public interface CalciteResource {
@BaseMessage("Null key of JSON object is not allowed")
ExInst<CalciteException> nullKeyOfJsonObjectNotAllowed();
+ @BaseMessage("Timeout of ''{0}'' ms for query execution is reached. Query
execution started at ''{1}''")
+ ExInst<CalciteException> queryExecutionTimeoutReached(String timeout, String
queryStart);
+
@BaseMessage("While executing SQL [{0}] on JDBC sub-schema")
ExInst<RuntimeException> exceptionWhilePerformingQueryOnJdbcSubSchema(String
sql);
}
diff --git
a/core/src/main/java/org/apache/calcite/runtime/ResultSetEnumerable.java
b/core/src/main/java/org/apache/calcite/runtime/ResultSetEnumerable.java
index 52c11f9..d8f005b 100644
--- a/core/src/main/java/org/apache/calcite/runtime/ResultSetEnumerable.java
+++ b/core/src/main/java/org/apache/calcite/runtime/ResultSetEnumerable.java
@@ -49,6 +49,7 @@ import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
@@ -67,6 +68,10 @@ public class ResultSetEnumerable<T> extends
AbstractEnumerable<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(
ResultSetEnumerable.class);
+ private Long queryStart;
+ private long timeout;
+ private boolean timeoutSetFailed;
+
private static final Function1<ResultSet, Function0<Object>>
AUTO_ROW_BUILDER_FACTORY =
resultSet -> {
final ResultSetMetaData metaData;
@@ -129,20 +134,20 @@ public class ResultSetEnumerable<T> extends
AbstractEnumerable<T> {
}
/** Creates an ResultSetEnumerable. */
- public static Enumerable<Object> of(DataSource dataSource, String sql) {
+ public static ResultSetEnumerable<Object> of(DataSource dataSource, String
sql) {
return of(dataSource, sql, AUTO_ROW_BUILDER_FACTORY);
}
/** Creates an ResultSetEnumerable that retrieves columns as specific
* Java types. */
- public static Enumerable<Object> of(DataSource dataSource, String sql,
+ public static ResultSetEnumerable<Object> of(DataSource dataSource, String
sql,
Primitive[] primitives) {
return of(dataSource, sql, primitiveRowBuilderFactory(primitives));
}
/** Executes a SQL query and returns the results as an enumerator, using a
* row builder to convert JDBC column values into rows. */
- public static <T> Enumerable<T> of(
+ public static <T> ResultSetEnumerable<T> of(
DataSource dataSource,
String sql,
Function1<ResultSet, Function0<T>> rowBuilderFactory) {
@@ -154,7 +159,7 @@ public class ResultSetEnumerable<T> extends
AbstractEnumerable<T> {
*
* <p>It uses a {@link PreparedStatement} for computing the query result,
* and that means that it can bind parameters. */
- public static <T> Enumerable<T> of(
+ public static <T> ResultSetEnumerable<T> of(
DataSource dataSource,
String sql,
Function1<ResultSet, Function0<T>> rowBuilderFactory,
@@ -162,6 +167,19 @@ public class ResultSetEnumerable<T> extends
AbstractEnumerable<T> {
return new ResultSetEnumerable<>(dataSource, sql, rowBuilderFactory,
consumer);
}
+ public void setTimeout(DataContext context) {
+ this.queryStart = (Long)
context.get(DataContext.Variable.UTC_TIMESTAMP.camelName);
+ Object timeout = context.get(DataContext.Variable.TIMEOUT.camelName);
+ if (timeout instanceof Long) {
+ this.timeout = (Long) timeout;
+ } else {
+ if (timeout != null) {
+ LOGGER.debug("Variable.TIMEOUT should be `long`. Given value was {}",
timeout);
+ }
+ this.timeout = 0;
+ }
+ }
+
/** Called from generated code that proposes to create a
* {@code ResultSetEnumerable} over a prepared statement. */
public static PreparedStatementEnricher createEnricher(Integer[] indexes,
@@ -286,10 +304,28 @@ public class ResultSetEnumerable<T> extends
AbstractEnumerable<T> {
}
private void setTimeoutIfPossible(Statement statement) throws SQLException {
+ if (timeout == 0) {
+ return;
+ }
+ long now = System.currentTimeMillis();
+ long secondsLeft = (queryStart + timeout - now) / 1000;
+ if (secondsLeft <= 0) {
+ throw Static.RESOURCE.queryExecutionTimeoutReached(
+ String.valueOf(timeout),
+ String.valueOf(Instant.ofEpochMilli(queryStart))).ex();
+ }
+ if (secondsLeft > Integer.MAX_VALUE) {
+ // Just ignore the timeout if it happens to be too big, we can't squeeze
it into int
+ return;
+ }
try {
- statement.setQueryTimeout(10);
+ statement.setQueryTimeout((int) secondsLeft);
} catch (SQLFeatureNotSupportedException e) {
- LOGGER.debug("Failed to set query timeout.");
+ if (!timeoutSetFailed && LOGGER.isDebugEnabled()) {
+ // We don't really want to print this again and again if enumerable is
used multiple times
+ LOGGER.debug("Failed to set query timeout " + secondsLeft + "
seconds", e);
+ timeoutSetFailed = true;
+ }
}
}
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index cfadcfe..97b47dc 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -146,6 +146,8 @@ public enum BuiltInMethod {
JDBC_SCHEMA_DATA_SOURCE(JdbcSchema.class, "getDataSource"),
ROW_VALUE(Row.class, "getObject", int.class),
ROW_AS_COPY(Row.class, "asCopy", Object[].class),
+ RESULT_SET_ENUMERABLE_SET_TIMEOUT(ResultSetEnumerable.class, "setTimeout",
+ DataContext.class),
RESULT_SET_ENUMERABLE_OF(ResultSetEnumerable.class, "of", DataSource.class,
String.class, Function1.class),
RESULT_SET_ENUMERABLE_OF_PREPARED(ResultSetEnumerable.class, "of",
diff --git
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 1ec7c95..a717d16 100644
---
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -275,5 +275,6 @@ IllegalEmptyBehaviorInJsonQueryFunc=Illegal empty behavior
''{0}'' specified in
ArrayOrObjectValueRequiredInStrictModeOfJsonQueryFunc=Strict jsonpath mode
requires array or object value, and the actual value is: ''{0}''
IllegalErrorBehaviorInJsonQueryFunc=Illegal error behavior ''{0}'' specified
in JSON_VALUE function
NullKeyOfJsonObjectNotAllowed=Null key of JSON object is not allowed
+QueryExecutionTimeoutReached=Timeout of ''{0}'' ms for query execution is
reached. Query execution started at ''{1}''
ExceptionWhilePerformingQueryOnJdbcSubSchema = While executing SQL [{0}] on
JDBC sub-schema
# End CalciteResource.properties