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

Reply via email to