diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4611dca..74ffc98d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
1. [#306](https://github.com/InfluxCommunity/influxdb3-java/pull/306): Improve closing of Arrow `FlightStream`.
+### Bug Fixes
+
+1. [#310](https://github.com/InfluxCommunity/influxdb3-java/pull/310): Ensure `QueryOptions` objects are left unchanged within the `queryData` implementation.
+
## 1.5.0 [2025-10-22]
### Features
diff --git a/src/main/java/com/influxdb/v3/client/internal/GrpcCallOptions.java b/src/main/java/com/influxdb/v3/client/internal/GrpcCallOptions.java
index 365be44e..acc5f344 100644
--- a/src/main/java/com/influxdb/v3/client/internal/GrpcCallOptions.java
+++ b/src/main/java/com/influxdb/v3/client/internal/GrpcCallOptions.java
@@ -21,6 +21,7 @@
*/
package com.influxdb.v3.client.internal;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -226,6 +227,11 @@ public Builder() {
/**
* Sets the absolute deadline for a rpc call.
*
+ *
Please note the preferred approach is to set a queryTimeout
+ * Duration value globally in the ClientConfig
+ * ({@link com.influxdb.v3.client.config.ClientConfig.Builder#queryTimeout(Duration)}).
+ * This value will then be used to calculate a new Deadline with each call.
+ *
* @param deadline The deadline
* @return this
*/
@@ -234,6 +240,17 @@ public Builder withDeadline(final @Nonnull Deadline deadline) {
return this;
}
+ /**
+ * Unsets absolute deadline. Note deadline may have been set
+ * via {@link #fromGrpcCallOptions(GrpcCallOptions)} method.
+ *
+ * @return this
+ */
+ public Builder withoutDeadline() {
+ this.deadline = null;
+ return this;
+ }
+
/**
* Sets an {@code executor} to be used instead of the default
* executor specified with {@link ManagedChannelBuilder#executor}.
diff --git a/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java b/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java
index 5eb05f23..7b7034c0 100644
--- a/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java
+++ b/src/main/java/com/influxdb/v3/client/internal/InfluxDBClientImpl.java
@@ -398,13 +398,6 @@ private Stream queryData(@Nonnull final String query,
Arguments.checkNotNull(parameters, "parameters");
Arguments.checkNotNull(options, "options");
- if (options.grpcCallOptions().getDeadline() == null && config.getQueryTimeout() != null) {
- options.setGrpcCallOptions(new GrpcCallOptions.Builder()
- .fromGrpcCallOptions(options.grpcCallOptions())
- .withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS))
- .build());
- }
-
if (closed) {
throw new IllegalStateException("InfluxDBClient has been closed.");
}
@@ -421,7 +414,31 @@ private Stream queryData(@Nonnull final String query,
}
});
- CallOption[] callOptions = options.grpcCallOptions().getCallOptions();
+ GrpcCallOptions.Builder builder = new GrpcCallOptions.Builder()
+ .fromGrpcCallOptions(options.grpcCallOptions());
+
+ if (config.getQueryTimeout() == null) {
+ if (options.grpcCallOptions().getDeadline() != null
+ && options.grpcCallOptions().getDeadline().timeRemaining(TimeUnit.MILLISECONDS) <= 0) {
+ LOG.warning("Query timeout "
+ + options.grpcCallOptions().getDeadline()
+ + " is 0 or negative and will be ignored.");
+ builder.withoutDeadline();
+ }
+ } else {
+ if (options.grpcCallOptions().getDeadline() == null) {
+ builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS));
+ } else if (options.grpcCallOptions().getDeadline().timeRemaining(TimeUnit.MILLISECONDS) <= 0) {
+ LOG.warning("Query timeout "
+ + options.grpcCallOptions().getDeadline()
+ + " is 0 or negative. Using config.queryTimeout "
+ + config.getQueryTimeout()
+ + " instead.");
+ builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS));
+ }
+ }
+
+ CallOption[] callOptions = builder.build().getCallOptions();
return flightSqlClient.execute(
query,
diff --git a/src/main/java/com/influxdb/v3/client/query/QueryOptions.java b/src/main/java/com/influxdb/v3/client/query/QueryOptions.java
index 02f2a200..a2611c0b 100644
--- a/src/main/java/com/influxdb/v3/client/query/QueryOptions.java
+++ b/src/main/java/com/influxdb/v3/client/query/QueryOptions.java
@@ -22,6 +22,7 @@
package com.influxdb.v3.client.query;
import java.util.Map;
+import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@@ -190,4 +191,24 @@ public GrpcCallOptions grpcCallOptions() {
private boolean isNotDefined(final String option) {
return option == null || option.isEmpty();
}
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ QueryOptions that = (QueryOptions) o;
+ return Objects.equals(this.database, that.database)
+ && Objects.equals(this.queryType, that.queryType)
+ && Objects.equals(this.headers, that.headers)
+ && Objects.equals(this.grpcCallOptions, that.grpcCallOptions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(database, queryType, headers, grpcCallOptions);
+ }
}
diff --git a/src/test/java/com/influxdb/v3/client/ITQueryWrite.java b/src/test/java/com/influxdb/v3/client/ITQueryWrite.java
index 49e9110c..d9bf4dd2 100644
--- a/src/test/java/com/influxdb/v3/client/ITQueryWrite.java
+++ b/src/test/java/com/influxdb/v3/client/ITQueryWrite.java
@@ -412,7 +412,7 @@ public void queryTimeoutSuperceededByGrpcOptTest() {
.host(System.getenv("TESTING_INFLUXDB_URL"))
.token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
.database(System.getenv("TESTING_INFLUXDB_DATABASE"))
- .queryTimeout(Duration.ofSeconds(3))
+ .queryTimeout(Duration.ofNanos(3))
.build());
String measurement = "timeout_test_" + Math.round(Math.random() * 100_000);
@@ -423,20 +423,18 @@ public void queryTimeoutSuperceededByGrpcOptTest() {
QueryOptions queryOptions = QueryOptions.defaultQueryOptions();
queryOptions.setGrpcCallOptions(new GrpcCallOptions.Builder()
- .withDeadline(Deadline.after(5000, TimeUnit.NANOSECONDS))
+ .withDeadline(Deadline.after(500, TimeUnit.MILLISECONDS))
.build()
);
- Throwable thrown = catchThrowable(() -> {
- Stream