This is an automated email from the ASF dual-hosted git repository.
kfaraz pushed a commit to branch 38.0.0
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/38.0.0 by this push:
new 67bb967e2fe fix: fix query context precedence layer for
datasource-level per-segment timeout (#19654)
67bb967e2fe is described below
commit 67bb967e2fe699d92283740207114d17252c3f08
Author: jtuglu1 <[email protected]>
AuthorDate: Fri Jul 31 03:43:17 2026 -0700
fix: fix query context precedence layer for datasource-level per-segment
timeout (#19654)
Currently, per-datasource, per-segment timeout can be not set if there's a
hardcoded
static default query context
(`druid.query.default.context.perSegmentTimeout`).
This patch ensures the evaluation precedence for the per-segment timeout is
as follows
(from highest to lowest):
1. Query-specified context values (passed in the query payload)
2. Dynamic configs (broker per-datasource per-segment timeout, broker
dynamic config, etc.).
Former beats the latter for this particular config.
3. Static defaults specified at process boot
(`druid.query.default.context.perSegmentTimeout`)
4. Default values specified in the application code.
Also adds the perSegmentTimeout context key so it's easy to auto-complete
in the UI.
(cherry picked from commit 4e15d6ef99d58ebea27e1bb25291aab1ace3867d)
---
.../apache/druid/query/QueryConfigProvider.java | 2 +-
.../druid/client/BrokerViewOfBrokerConfig.java | 45 +++++--
.../org/apache/druid/server/QueryLifecycle.java | 107 +++++++--------
.../apache/druid/server/QueryLifecycleFactory.java | 23 +---
.../druid/server/broker/BrokerDynamicConfig.java | 36 +++++
.../druid/server/broker/QueryConfigSnapshot.java | 91 +++++++++++++
.../druid/client/BrokerViewOfBrokerConfigTest.java | 52 ++++++++
.../server/PerSegmentTimeoutInjectionTest.java | 146 +++++++++------------
.../apache/druid/server/QueryLifecycleTest.java | 20 +--
.../org/apache/druid/server/QueryResourceTest.java | 13 +-
.../server/broker/BrokerDynamicConfigTest.java | 45 +++++++
.../druid/sql/calcite/run/NativeQueryMaker.java | 4 +-
.../query-context-completions.ts | 4 +
13 files changed, 398 insertions(+), 190 deletions(-)
diff --git
a/processing/src/main/java/org/apache/druid/query/QueryConfigProvider.java
b/processing/src/main/java/org/apache/druid/query/QueryConfigProvider.java
index 6b533ab6a37..1559beb0732 100644
--- a/processing/src/main/java/org/apache/druid/query/QueryConfigProvider.java
+++ b/processing/src/main/java/org/apache/druid/query/QueryConfigProvider.java
@@ -22,7 +22,7 @@ package org.apache.druid.query;
import java.util.Map;
/**
- * Provides the default query config applied to all incoming queries before
per-query overrides are merged in.
+ * Provides the default query context applied to all incoming queries before
per-query overrides are merged in.
*
* <p>On non-broker nodes this is backed by static runtime properties ({@link
DefaultQueryConfig}).
* On brokers, it is backed by {@code BrokerViewOfBrokerConfig}, which merges
the static defaults with
diff --git
a/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
b/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
index b67b4bc46da..0c6f52805bb 100644
--- a/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
+++ b/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
@@ -36,6 +36,7 @@ import org.apache.druid.rpc.ServiceClientFactory;
import org.apache.druid.rpc.ServiceLocator;
import org.apache.druid.rpc.StandardRetryPolicy;
import org.apache.druid.server.broker.BrokerDynamicConfig;
+import org.apache.druid.server.broker.QueryConfigSnapshot;
import javax.validation.constraints.NotNull;
import java.util.Map;
@@ -54,11 +55,13 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
private final DefaultQueryConfig defaultQueryConfig;
/**
- * Pre-computed merge of {@link DefaultQueryConfig#getContext()} and
+ * The dynamic config plus the merge of {@link
DefaultQueryConfig#getContext()} and
* {@link BrokerDynamicConfig#getQueryContext()}, recomputed on each config
sync.
- * Dynamic config values override static defaults. {@link QueryContext}
provides immutability.
+ *
+ * <p>volatile, not synchronized: read on the query hot path, see {@link
BaseBrokerViewOfConfig}. Both halves
+ * live in one field so a query cannot observe them from different
generations.
*/
- private volatile QueryContext resolvedDefaultQueryContext;
+ private volatile QueryConfigSnapshot querySnapshot;
@Inject
public BrokerViewOfBrokerConfig(
@@ -69,7 +72,7 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
)
{
this.defaultQueryConfig = defaultQueryConfig;
- this.resolvedDefaultQueryContext =
QueryContext.of(defaultQueryConfig.getContext());
+ this.querySnapshot = new
QueryConfigSnapshot(QueryContext.of(defaultQueryConfig.getContext()).asMap(),
null);
this.coordinatorClient =
new CoordinatorClientImpl(
clientFactory.makeClient(
@@ -89,7 +92,7 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
{
this.coordinatorClient = coordinatorClient;
this.defaultQueryConfig = defaultQueryConfig;
- this.resolvedDefaultQueryContext =
QueryContext.of(defaultQueryConfig.getContext());
+ this.querySnapshot = new
QueryConfigSnapshot(QueryContext.of(defaultQueryConfig.getContext()).asMap(),
null);
}
@Override
@@ -109,13 +112,16 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
* resolved default query context by merging static defaults with dynamic
overrides.
*/
@Override
- public synchronized void setDynamicConfig(@NotNull BrokerDynamicConfig
updatedConfig)
+ public void setDynamicConfig(@NotNull BrokerDynamicConfig updatedConfig)
{
super.setDynamicConfig(updatedConfig);
- resolvedDefaultQueryContext = QueryContext.of(QueryContexts.override(
- defaultQueryConfig.getContext(),
- updatedConfig.getQueryContext().asMap()
- ));
+ querySnapshot = new QueryConfigSnapshot(
+ QueryContext.of(QueryContexts.override(
+ defaultQueryConfig.getContext(),
+ updatedConfig.getQueryContext().asMap()
+ )).asMap(),
+ updatedConfig
+ );
}
/**
@@ -125,6 +131,23 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
@Override
public Map<String, Object> getContext()
{
- return resolvedDefaultQueryContext.asMap();
+ return querySnapshot.getResolvedDefaultQueryContext();
+ }
+
+ /**
+ * Snapshot for a single query to resolve its context and blocklist against,
instead of re-reading the live config.
+ */
+ public QueryConfigSnapshot snapshotForQuery()
+ {
+ return querySnapshot;
+ }
+
+ /**
+ * Reads through {@link #querySnapshot} so this and {@link
#snapshotForQuery()} always agree.
+ */
+ @Override
+ public BrokerDynamicConfig getDynamicConfig()
+ {
+ return querySnapshot.getDynamicConfig();
}
}
diff --git a/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
b/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
index fc7d896df83..0f970a7fe0c 100644
--- a/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
+++ b/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
@@ -37,9 +37,7 @@ import org.apache.druid.query.BaseQuery;
import org.apache.druid.query.DruidMetrics;
import org.apache.druid.query.GenericQueryMetricsFactory;
import org.apache.druid.query.Query;
-import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContext;
-import org.apache.druid.query.QueryContexts;
import org.apache.druid.query.QueryInterruptedException;
import org.apache.druid.query.QueryMetrics;
import org.apache.druid.query.QueryPlus;
@@ -49,7 +47,7 @@ import org.apache.druid.query.QueryTimeoutException;
import org.apache.druid.query.QueryToolChest;
import org.apache.druid.query.context.ResponseContext;
import org.apache.druid.query.policy.PolicyEnforcer;
-import org.apache.druid.server.broker.PerSegmentTimeoutConfig;
+import org.apache.druid.server.broker.QueryConfigSnapshot;
import org.apache.druid.server.log.RequestLogger;
import org.apache.druid.server.security.Action;
import org.apache.druid.server.security.AuthConfig;
@@ -64,7 +62,6 @@ import
org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -99,11 +96,9 @@ public class QueryLifecycle
private final ServiceEmitter emitter;
private final RequestLogger requestLogger;
private final AuthorizerMapper authorizerMapper;
- private final QueryConfigProvider queryConfigProvider;
private final AuthConfig authConfig;
private final PolicyEnforcer policyEnforcer;
- private final List<QueryBlocklistRule> queryBlocklist;
- private final Map<String, PerSegmentTimeoutConfig> perSegmentTimeoutConfig;
+ private final QueryConfigSnapshot configSnapshot;
private final long startMs;
private final long startNs;
@@ -113,8 +108,11 @@ public class QueryLifecycle
@MonotonicNonNull
private Query<?> baseQuery;
+ /**
+ * Context keys as received, i.e. the candidate set for {@link
AuthConfig#contextKeysToAuthorize}.
+ */
@MonotonicNonNull
- private Set<String> userContextKeys;
+ private Set<String> queryContextKeysToAuthorize;
public QueryLifecycle(
final QueryRunnerFactoryConglomerate conglomerate,
@@ -123,11 +121,9 @@ public class QueryLifecycle
final ServiceEmitter emitter,
final RequestLogger requestLogger,
final AuthorizerMapper authorizerMapper,
- final QueryConfigProvider queryConfigProvider,
final AuthConfig authConfig,
final PolicyEnforcer policyEnforcer,
- final List<QueryBlocklistRule> queryBlocklist,
- final Map<String, PerSegmentTimeoutConfig> perSegmentTimeoutConfig,
+ final QueryConfigSnapshot configSnapshot,
final long startMs,
final long startNs
)
@@ -138,11 +134,9 @@ public class QueryLifecycle
this.emitter = emitter;
this.requestLogger = requestLogger;
this.authorizerMapper = authorizerMapper;
- this.queryConfigProvider = queryConfigProvider;
this.authConfig = authConfig;
this.policyEnforcer = policyEnforcer;
- this.queryBlocklist = queryBlocklist;
- this.perSegmentTimeoutConfig = perSegmentTimeoutConfig;
+ this.configSnapshot = configSnapshot;
this.startMs = startMs;
this.startNs = startNs;
}
@@ -166,7 +160,21 @@ public class QueryLifecycle
final AuthorizationResult authorizationResult
)
{
- initialize(query);
+ return runSimple(query, authenticationResult, authorizationResult, null);
+ }
+
+ /**
+ * As {@link #runSimple(Query, AuthenticationResult, AuthorizationResult)},
but takes the context keys the client
+ * actually set. See {@link #initialize(Query, Set)}.
+ */
+ public <T> QueryResponse<T> runSimple(
+ final Query<T> query,
+ final AuthenticationResult authenticationResult,
+ final AuthorizationResult authorizationResult,
+ @Nullable final Set<String> clientProvidedQueryContextKeys
+ )
+ {
+ initialize(query, clientProvidedQueryContextKeys);
final Sequence<T> results;
@@ -212,61 +220,40 @@ public class QueryLifecycle
* @throws DruidException if the current state is not NEW, which indicates a
bug
*/
public void initialize(final Query<?> baseQuery)
+ {
+ initialize(baseQuery, null);
+ }
+
+ /**
+ * As {@link #initialize(Query)}, but takes the context keys the client
actually set. Pass {@code null} to treat the
+ * whole context as client-set. The SQL layer must pass the real keys, since
it merges defaults into the context
+ * and those should still be overridable by dynamic config.
+ *
+ * @throws DruidException if the current state is not NEW, which indicates a
bug
+ */
+ public void initialize(final Query<?> baseQuery, @Nullable final Set<String>
clientProvidedQueryContextKeys)
{
transition(State.NEW, State.INITIALIZED);
- userContextKeys = new HashSet<>(baseQuery.getContext().keySet());
+ final Map<String, Object> baseContext = baseQuery.getContext();
+ queryContextKeysToAuthorize = new HashSet<>(baseContext.keySet());
+
+ final Set<String> effectiveClientProvidedQueryContextKeys =
+ clientProvidedQueryContextKeys != null ?
clientProvidedQueryContextKeys : baseContext.keySet();
+
String queryId = baseQuery.getId();
if (Strings.isNullOrEmpty(queryId)) {
queryId = UUID.randomUUID().toString();
}
- // Start with system defaults, apply per-datasource override, then user
context wins
- Map<String, Object> contextWithDefaults = new
HashMap<>(queryConfigProvider.getContext());
- applyPerDatasourcePerSegmentTimeout(baseQuery, contextWithDefaults,
queryId);
- Map<String, Object> finalContext =
QueryContexts.override(contextWithDefaults, baseQuery.getContext());
+ final Map<String, Object> finalContext =
+ configSnapshot.resolveContext(baseQuery,
effectiveClientProvidedQueryContextKeys);
finalContext.put(BaseQuery.QUERY_ID, queryId);
this.baseQuery = baseQuery.withOverriddenContext(finalContext);
this.toolChest = conglomerate.getToolChest(this.baseQuery);
}
- /**
- * If a per-datasource per-segment timeout is configured, injects it into
the context defaults.
- * User context (applied later via {@link QueryContexts#override}) will
override this if set explicitly.
- * In monitorOnly mode, logs the configured timeout but does not inject it.
- *
- * For queries involving multiple datasources (e.g., joins or unions), the
timeout from the first matching datasource is applied
- * since getTableNames() returns a Set, the match order is non-deterministic.
- */
- private void applyPerDatasourcePerSegmentTimeout(
- final Query<?> query,
- final Map<String, Object> contextWithDefaults,
- final String queryId
- )
- {
- if (perSegmentTimeoutConfig.isEmpty()) {
- return;
- }
-
- for (String tableName : query.getDataSource().getTableNames()) {
- PerSegmentTimeoutConfig dsConfig =
perSegmentTimeoutConfig.get(tableName);
- if (dsConfig != null) {
- if (dsConfig.isMonitorOnly()) {
- log.debug(
- "Per-segment timeout [%d ms] configured for datasource [%s] in
monitorOnly mode (not enforced) for query [%s].",
- dsConfig.getPerSegmentTimeoutMs(),
- tableName,
- queryId
- );
- } else {
- contextWithDefaults.put(QueryContexts.PER_SEGMENT_TIMEOUT_KEY,
dsConfig.getPerSegmentTimeoutMs());
- }
- return;
- }
- }
- }
-
/**
* Returns {@link AuthorizationResult} based on {@code
DRUID_AUTHENTICATION_RESULT} in the given request, base query
* would be transformed with restrictions on the AuthorizationResult.
@@ -290,7 +277,7 @@ public class QueryLifecycle
AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR
),
Iterables.transform(
- authConfig.contextKeysToAuthorize(userContextKeys),
+ authConfig.contextKeysToAuthorize(queryContextKeysToAuthorize),
contextParam -> new ResourceAction(new Resource(contextParam,
ResourceType.QUERY_CONTEXT), Action.WRITE)
)
);
@@ -328,7 +315,7 @@ public class QueryLifecycle
AuthorizationUtils.DATASOURCE_READ_RA_GENERATOR
),
Iterables.transform(
- authConfig.contextKeysToAuthorize(userContextKeys),
+ authConfig.contextKeysToAuthorize(queryContextKeysToAuthorize),
contextParam -> new ResourceAction(new Resource(contextParam,
ResourceType.QUERY_CONTEXT), Action.WRITE)
)
);
@@ -364,7 +351,9 @@ public class QueryLifecycle
*/
private void checkQueryBlocklist()
{
- if (queryBlocklist == null || queryBlocklist.isEmpty()) {
+ final List<QueryBlocklistRule> queryBlocklist =
configSnapshot.getQueryBlocklist();
+
+ if (queryBlocklist.isEmpty()) {
return;
}
diff --git
a/server/src/main/java/org/apache/druid/server/QueryLifecycleFactory.java
b/server/src/main/java/org/apache/druid/server/QueryLifecycleFactory.java
index 57fcd1b47e9..d22d24ed3ef 100644
--- a/server/src/main/java/org/apache/druid/server/QueryLifecycleFactory.java
+++ b/server/src/main/java/org/apache/druid/server/QueryLifecycleFactory.java
@@ -28,15 +28,12 @@ import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryRunnerFactoryConglomerate;
import org.apache.druid.query.QuerySegmentWalker;
import org.apache.druid.query.policy.PolicyEnforcer;
-import org.apache.druid.server.broker.PerSegmentTimeoutConfig;
+import org.apache.druid.server.broker.QueryConfigSnapshot;
import org.apache.druid.server.log.RequestLogger;
import org.apache.druid.server.security.AuthConfig;
import org.apache.druid.server.security.AuthorizerMapper;
import javax.annotation.Nullable;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
@LazySingleton
public class QueryLifecycleFactory
@@ -80,15 +77,11 @@ public class QueryLifecycleFactory
public QueryLifecycle factorize()
{
- final List<QueryBlocklistRule> queryBlocklist;
- final Map<String, PerSegmentTimeoutConfig> perSegmentTimeoutConfig;
- if (brokerViewOfBrokerConfig != null &&
brokerViewOfBrokerConfig.getDynamicConfig() != null) {
- queryBlocklist =
brokerViewOfBrokerConfig.getDynamicConfig().getQueryBlocklist();
- perSegmentTimeoutConfig =
brokerViewOfBrokerConfig.getDynamicConfig().getPerSegmentTimeoutConfig();
- } else {
- queryBlocklist = Collections.emptyList();
- perSegmentTimeoutConfig = Collections.emptyMap();
- }
+ // Read once per query so the whole lifecycle sees one config, even if it
is swapped mid-query.
+ final QueryConfigSnapshot configSnapshot =
+ brokerViewOfBrokerConfig == null
+ ? new QueryConfigSnapshot(queryConfigProvider.getContext(), null)
+ : brokerViewOfBrokerConfig.snapshotForQuery();
return new QueryLifecycle(
conglomerate,
@@ -97,11 +90,9 @@ public class QueryLifecycleFactory
emitter,
requestLogger,
authorizerMapper,
- queryConfigProvider,
authConfig,
policyEnforcer,
- queryBlocklist,
- perSegmentTimeoutConfig,
+ configSnapshot,
System.currentTimeMillis(),
System.nanoTime()
);
diff --git
a/server/src/main/java/org/apache/druid/server/broker/BrokerDynamicConfig.java
b/server/src/main/java/org/apache/druid/server/broker/BrokerDynamicConfig.java
index 57200e729d3..37634048de2 100644
---
a/server/src/main/java/org/apache/druid/server/broker/BrokerDynamicConfig.java
+++
b/server/src/main/java/org/apache/druid/server/broker/BrokerDynamicConfig.java
@@ -22,7 +22,10 @@ package org.apache.druid.server.broker;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.druid.common.config.Configs;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.apache.druid.query.Query;
import org.apache.druid.query.QueryContext;
+import org.apache.druid.query.QueryContexts;
import org.apache.druid.server.QueryBlocklistRule;
import javax.annotation.Nullable;
@@ -40,6 +43,8 @@ import java.util.Objects;
*/
public class BrokerDynamicConfig
{
+ private static final Logger log = new Logger(BrokerDynamicConfig.class);
+
public static final String CONFIG_KEY = "broker.config";
/**
@@ -91,6 +96,37 @@ public class BrokerDynamicConfig
return perSegmentTimeoutConfig;
}
+ /**
+ * Query context overrides (e.g. per-segment timeout) for the datasources
the query targets. With multiple
+ * datasources the first match wins, in non-deterministic order.
+ */
+ public QueryContext getContextOverridesForQuery(Query<?> query)
+ {
+ if (perSegmentTimeoutConfig.isEmpty()) {
+ return QueryContext.empty();
+ }
+
+ for (String tableName : query.getDataSource().getTableNames()) {
+ PerSegmentTimeoutConfig dataSourceTimeoutConfig =
perSegmentTimeoutConfig.get(tableName);
+ if (dataSourceTimeoutConfig != null) {
+ if (dataSourceTimeoutConfig.isMonitorOnly()) {
+ // monitorOnly is documented as "logged but not enforced", so this
log is its only effect.
+ log.debug(
+ "Per-segment timeout[%d ms] configured for datasource[%s] in
monitorOnly mode (not enforced) for query[%s].",
+ dataSourceTimeoutConfig.getPerSegmentTimeoutMs(),
+ tableName,
+ query.getId()
+ );
+ return QueryContext.empty();
+ }
+ return QueryContext.of(
+ Map.of(QueryContexts.PER_SEGMENT_TIMEOUT_KEY,
dataSourceTimeoutConfig.getPerSegmentTimeoutMs())
+ );
+ }
+ }
+ return QueryContext.empty();
+ }
+
@Override
public boolean equals(Object o)
{
diff --git
a/server/src/main/java/org/apache/druid/server/broker/QueryConfigSnapshot.java
b/server/src/main/java/org/apache/druid/server/broker/QueryConfigSnapshot.java
new file mode 100644
index 00000000000..882a64543f6
--- /dev/null
+++
b/server/src/main/java/org/apache/druid/server/broker/QueryConfigSnapshot.java
@@ -0,0 +1,91 @@
+/*
+ * 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
+ *
+ * http://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.druid.server.broker;
+
+import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryContexts;
+import org.apache.druid.server.QueryBlocklistRule;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A snapshot of the {@link BrokerDynamicConfig} (null on non-Broker nodes)
+ * and the resolved default query context that is used for the entire {@code
QueryLifecycle}
+ * of a single query.
+ */
+public class QueryConfigSnapshot
+{
+ /** Already resolved against {@link BrokerDynamicConfig#getQueryContext()}.
*/
+ private final Map<String, Object> resolvedDefaultQueryContext;
+ @Nullable
+ private final BrokerDynamicConfig dynamicConfig;
+
+ public QueryConfigSnapshot(
+ Map<String, Object> resolvedDefaultQueryContext,
+ @Nullable BrokerDynamicConfig dynamicConfig
+ )
+ {
+ this.resolvedDefaultQueryContext = resolvedDefaultQueryContext;
+ this.dynamicConfig = dynamicConfig;
+ }
+
+ public Map<String, Object> getResolvedDefaultQueryContext()
+ {
+ return resolvedDefaultQueryContext;
+ }
+
+ @Nullable
+ public BrokerDynamicConfig getDynamicConfig()
+ {
+ return dynamicConfig;
+ }
+
+ /**
+ * The final query context for the given query. Precedence, highest to
lowest:
+ * <ol>
+ * <li>Keys the client set on the query payload</li>
+ * <li>Per-query overrides from {@link
BrokerDynamicConfig#getContextOverridesForQuery}</li>
+ * <li>Remaining keys on the query context (defaults merged in by the SQL
layer)</li>
+ * <li>{@link #resolvedDefaultQueryContext}, i.e. runtime properties
overridden by
+ * {@link BrokerDynamicConfig#getQueryContext()}</li>
+ * </ol>
+ */
+ public Map<String, Object> resolveContext(Query<?> query, Set<String>
clientProvidedQueryContextKeys)
+ {
+ final Map<String, Object> result =
QueryContexts.override(resolvedDefaultQueryContext, query.getContext());
+ if (dynamicConfig != null) {
+ for (Map.Entry<String, Object> override :
dynamicConfig.getContextOverridesForQuery(query).asMap().entrySet()) {
+ if (!clientProvidedQueryContextKeys.contains(override.getKey())) {
+ result.put(override.getKey(), override.getValue());
+ }
+ }
+ }
+ return result;
+ }
+
+ public List<QueryBlocklistRule> getQueryBlocklist()
+ {
+ return dynamicConfig == null ? Collections.emptyList() :
dynamicConfig.getQueryBlocklist();
+ }
+}
diff --git
a/server/src/test/java/org/apache/druid/client/BrokerViewOfBrokerConfigTest.java
b/server/src/test/java/org/apache/druid/client/BrokerViewOfBrokerConfigTest.java
index c4877220ffc..54318f75590 100644
---
a/server/src/test/java/org/apache/druid/client/BrokerViewOfBrokerConfigTest.java
+++
b/server/src/test/java/org/apache/druid/client/BrokerViewOfBrokerConfigTest.java
@@ -19,12 +19,16 @@
package org.apache.druid.client;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import org.apache.druid.client.coordinator.CoordinatorClient;
import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.QueryContext;
+import org.apache.druid.server.DefaultQueryBlocklistRule;
import org.apache.druid.server.broker.BrokerDynamicConfig;
+import org.apache.druid.server.broker.QueryConfigSnapshot;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -82,4 +86,52 @@ public class BrokerViewOfBrokerConfigTest
target.setDynamicConfig(BrokerDynamicConfig.builder().build());
Assert.assertEquals(defaultQueryConfig.getContext(), target.getContext());
}
+
+ @Test
+ public void testSnapshotBeforeFirstSyncHasStaticDefaultsAndNoDynamicConfig()
+ {
+ final QueryConfigSnapshot snapshot = target.snapshotForQuery();
+ Assert.assertEquals(defaultQueryConfig.getContext(),
snapshot.getResolvedDefaultQueryContext());
+ Assert.assertTrue(snapshot.getQueryBlocklist().isEmpty());
+ }
+
+ @Test
+ public void testSnapshotAndGetContextStayInSync()
+ {
+ final BrokerDynamicConfig dynamicConfig =
+ BrokerDynamicConfig.builder()
+
.withQueryContext(QueryContext.of(ImmutableMap.of("priority", 5)))
+ .withQueryBlocklist(ImmutableList.of(
+ new DefaultQueryBlocklistRule("block-ds",
ImmutableSet.of("ds"), null, null)
+ ))
+ .build();
+ target.setDynamicConfig(dynamicConfig);
+
+ final QueryConfigSnapshot snapshot = target.snapshotForQuery();
+ Assert.assertSame(target.getContext(),
snapshot.getResolvedDefaultQueryContext());
+ Assert.assertEquals(dynamicConfig.getQueryBlocklist(),
snapshot.getQueryBlocklist());
+ Assert.assertEquals(5,
snapshot.getResolvedDefaultQueryContext().get("priority"));
+ Assert.assertSame(snapshot.getDynamicConfig(), target.getDynamicConfig());
+ }
+
+ @Test
+ public void testSnapshotIsUnaffectedByLaterConfigUpdate()
+ {
+ // A query holds its snapshot for the whole lifecycle, so a later swap
must not change it.
+ target.setDynamicConfig(
+ BrokerDynamicConfig.builder()
+
.withQueryContext(QueryContext.of(ImmutableMap.of("priority", 5)))
+ .build()
+ );
+ final QueryConfigSnapshot snapshot = target.snapshotForQuery();
+
+ target.setDynamicConfig(
+ BrokerDynamicConfig.builder()
+
.withQueryContext(QueryContext.of(ImmutableMap.of("priority", 9)))
+ .build()
+ );
+
+ Assert.assertEquals(5,
snapshot.getResolvedDefaultQueryContext().get("priority"));
+ Assert.assertEquals(9, target.getContext().get("priority"));
+ }
}
diff --git
a/server/src/test/java/org/apache/druid/server/PerSegmentTimeoutInjectionTest.java
b/server/src/test/java/org/apache/druid/server/PerSegmentTimeoutInjectionTest.java
index 440f065ba48..8b3aeec828b 100644
---
a/server/src/test/java/org/apache/druid/server/PerSegmentTimeoutInjectionTest.java
+++
b/server/src/test/java/org/apache/druid/server/PerSegmentTimeoutInjectionTest.java
@@ -23,7 +23,6 @@ import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.query.Druids;
import org.apache.druid.query.GenericQueryMetricsFactory;
-import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContexts;
import org.apache.druid.query.QueryRunnerFactoryConglomerate;
import org.apache.druid.query.QuerySegmentWalker;
@@ -31,7 +30,9 @@ import org.apache.druid.query.QueryToolChest;
import org.apache.druid.query.aggregation.CountAggregatorFactory;
import org.apache.druid.query.policy.NoopPolicyEnforcer;
import org.apache.druid.query.timeseries.TimeseriesQuery;
+import org.apache.druid.server.broker.BrokerDynamicConfig;
import org.apache.druid.server.broker.PerSegmentTimeoutConfig;
+import org.apache.druid.server.broker.QueryConfigSnapshot;
import org.apache.druid.server.log.RequestLogger;
import org.apache.druid.server.security.AuthConfig;
import org.apache.druid.server.security.AuthorizerMapper;
@@ -44,10 +45,17 @@ import org.junit.Test;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+/**
+ * Precedence of the per-query dynamic overrides at the {@link QueryLifecycle}
level: an override beats a
+ * non-client-provided value in the context, but a value the client set wins.
The datasource-matching logic itself is
+ * tested in {@code org.apache.druid.server.broker.BrokerDynamicConfigTest}.
+ */
public class PerSegmentTimeoutInjectionTest
{
private static final String DATASOURCE = "my_datasource";
+ private static final String KEY = QueryContexts.PER_SEGMENT_TIMEOUT_KEY;
private QueryRunnerFactoryConglomerate conglomerate;
private QuerySegmentWalker texasRanger;
@@ -55,7 +63,6 @@ public class PerSegmentTimeoutInjectionTest
private ServiceEmitter emitter;
private RequestLogger requestLogger;
private AuthorizerMapper authzMapper;
- private QueryConfigProvider queryConfig;
private QueryToolChest toolChest;
private final TimeseriesQuery baseQuery = Druids.newTimeseriesQueryBuilder()
@@ -68,142 +75,111 @@ public class PerSegmentTimeoutInjectionTest
public void setUp()
{
conglomerate = EasyMock.createMock(QueryRunnerFactoryConglomerate.class);
- texasRanger = EasyMock.createMock(QuerySegmentWalker.class);
- metricsFactory = EasyMock.createMock(GenericQueryMetricsFactory.class);
- emitter = EasyMock.createMock(ServiceEmitter.class);
+ texasRanger = EasyMock.createNiceMock(QuerySegmentWalker.class);
+ metricsFactory = EasyMock.createNiceMock(GenericQueryMetricsFactory.class);
+ emitter = EasyMock.createNiceMock(ServiceEmitter.class);
requestLogger = EasyMock.createNiceMock(RequestLogger.class);
authzMapper = EasyMock.createNiceMock(AuthorizerMapper.class);
- queryConfig = EasyMock.createMock(QueryConfigProvider.class);
toolChest = EasyMock.createNiceMock(QueryToolChest.class);
}
@After
public void tearDown()
{
- EasyMock.verify(conglomerate, queryConfig);
+ EasyMock.verify(conglomerate);
}
@Test
- public void testPerDatasourceTimeout_applied()
+ public void testDynamicOverrideAppliedWhenClientDidNotSet()
{
- Map<String, PerSegmentTimeoutConfig> config = Map.of(
- DATASOURCE, new PerSegmentTimeoutConfig(5000, false)
- );
-
- expectDefaults();
-
- QueryLifecycle lifecycle = createLifecycle(config);
+ QueryLifecycle lifecycle = createLifecycle(perSegmentTimeout(5000));
lifecycle.initialize(baseQuery);
Assert.assertEquals(5000L,
lifecycle.getQuery().context().getPerSegmentTimeout());
}
@Test
- public void testPerDatasourceTimeout_userOverrideWins()
+ public void testDynamicOverridesNonClientValueInContext()
{
- Map<String, PerSegmentTimeoutConfig> config = Map.of(
- DATASOURCE, new PerSegmentTimeoutConfig(5000, false)
- );
+ // SQL path: a default was merged into the context but the client did not
set it, so the dynamic override wins.
+ TimeseriesQuery query = baseQuery.withOverriddenContext(Map.of(KEY, "0"));
- expectDefaults();
+ QueryLifecycle lifecycle = createLifecycle(perSegmentTimeout(5000));
+ lifecycle.initialize(query, Collections.emptySet());
- TimeseriesQuery queryWithUserTimeout = baseQuery.withOverriddenContext(
- Map.of(QueryContexts.PER_SEGMENT_TIMEOUT_KEY, 2000L)
- );
+ Assert.assertEquals(5000L,
lifecycle.getQuery().context().getPerSegmentTimeout());
+ }
- QueryLifecycle lifecycle = createLifecycle(config);
- lifecycle.initialize(queryWithUserTimeout);
+ @Test
+ public void testClientProvidedValueWins()
+ {
+ TimeseriesQuery query = baseQuery.withOverriddenContext(Map.of(KEY,
2000L));
+
+ QueryLifecycle lifecycle = createLifecycle(perSegmentTimeout(5000));
+ lifecycle.initialize(query, Set.of(KEY));
Assert.assertEquals(2000L,
lifecycle.getQuery().context().getPerSegmentTimeout());
}
@Test
- public void testPerDatasourceTimeout_monitorOnlyDoesNotInject()
+ public void testDynamicOverrideBeatsStaticDefault()
{
- // monitorOnly=true: config exists but should NOT be enforced
- Map<String, PerSegmentTimeoutConfig> config = Map.of(
- DATASOURCE, new PerSegmentTimeoutConfig(5000, true)
- );
-
- expectDefaults();
-
- QueryLifecycle lifecycle = createLifecycle(config);
+ // The bug being fixed: the static default used to shadow the
per-datasource dynamic config.
+ QueryLifecycle lifecycle = createLifecycle(Map.of(KEY, 100L),
perSegmentTimeout(5000));
lifecycle.initialize(baseQuery);
- Assert.assertFalse(
- "monitorOnly should not inject perSegmentTimeout",
- lifecycle.getQuery().context().usePerSegmentTimeout()
- );
+ Assert.assertEquals(5000L,
lifecycle.getQuery().context().getPerSegmentTimeout());
}
@Test
- public void testPerDatasourceTimeout_noMatchingDatasource()
+ public void testStaticDefaultUsedWhenNoDatasourceOverride()
{
- Map<String, PerSegmentTimeoutConfig> config = Map.of(
- "other_datasource", new PerSegmentTimeoutConfig(5000, false)
- );
-
- expectDefaults();
-
- QueryLifecycle lifecycle = createLifecycle(config);
+ QueryLifecycle lifecycle = createLifecycle(Map.of(KEY, 100L),
BrokerDynamicConfig.builder().build());
lifecycle.initialize(baseQuery);
- Assert.assertFalse(lifecycle.getQuery().context().usePerSegmentTimeout());
+ Assert.assertEquals(100L,
lifecycle.getQuery().context().getPerSegmentTimeout());
}
@Test
- public void testPerDatasourceTimeout_overridesSystemDefault()
+ public void testMonitorOnlyIsNotEnforced()
{
- Map<String, PerSegmentTimeoutConfig> config = Map.of(
- DATASOURCE, new PerSegmentTimeoutConfig(5000, false)
- );
+ BrokerDynamicConfig dynamicConfig =
+ BrokerDynamicConfig.builder()
+ .withPerSegmentTimeoutConfig(Map.of(DATASOURCE, new
PerSegmentTimeoutConfig(5000L, true)))
+ .build();
- // System default sets perSegmentTimeout to 10000
- EasyMock.expect(queryConfig.getContext())
- .andReturn(Map.of(QueryContexts.PER_SEGMENT_TIMEOUT_KEY, 10000L))
- .anyTimes();
-
EasyMock.expect(conglomerate.getToolChest(EasyMock.anyObject())).andReturn(toolChest).once();
- EasyMock.replay(conglomerate, queryConfig);
-
- QueryLifecycle lifecycle = createLifecycle(config);
+ QueryLifecycle lifecycle = createLifecycle(dynamicConfig);
lifecycle.initialize(baseQuery);
- Assert.assertEquals(5000L,
lifecycle.getQuery().context().getPerSegmentTimeout());
+ Assert.assertFalse(lifecycle.getQuery().context().usePerSegmentTimeout());
}
@Test
- public void testPrecedence_userOverridesPerDatasourceOverridesSystemDefault()
+ public void testNoDynamicConfigMeansNoInjection()
{
- // System default: 10000, per-datasource: 5000, user: 2000 — user should
win
- Map<String, PerSegmentTimeoutConfig> config = Map.of(
- DATASOURCE, new PerSegmentTimeoutConfig(5000, false)
- );
-
- EasyMock.expect(queryConfig.getContext())
- .andReturn(Map.of(QueryContexts.PER_SEGMENT_TIMEOUT_KEY, 10000L))
- .anyTimes();
-
EasyMock.expect(conglomerate.getToolChest(EasyMock.anyObject())).andReturn(toolChest).once();
- EasyMock.replay(conglomerate, queryConfig);
-
- TimeseriesQuery queryWithUserTimeout = baseQuery.withOverriddenContext(
- Map.of(QueryContexts.PER_SEGMENT_TIMEOUT_KEY, 2000L)
- );
+ QueryLifecycle lifecycle = createLifecycle(null);
+ lifecycle.initialize(baseQuery);
- QueryLifecycle lifecycle = createLifecycle(config);
- lifecycle.initialize(queryWithUserTimeout);
+ Assert.assertFalse(lifecycle.getQuery().context().usePerSegmentTimeout());
+ }
- Assert.assertEquals(2000L,
lifecycle.getQuery().context().getPerSegmentTimeout());
+ private static BrokerDynamicConfig perSegmentTimeout(long timeoutMs)
+ {
+ return BrokerDynamicConfig.builder()
+ .withPerSegmentTimeoutConfig(Map.of(DATASOURCE,
new PerSegmentTimeoutConfig(timeoutMs, false)))
+ .build();
}
- private void expectDefaults()
+ private QueryLifecycle createLifecycle(BrokerDynamicConfig dynamicConfig)
{
- EasyMock.expect(queryConfig.getContext()).andReturn(Map.of()).anyTimes();
-
EasyMock.expect(conglomerate.getToolChest(EasyMock.anyObject())).andReturn(toolChest).once();
- EasyMock.replay(conglomerate, queryConfig);
+ return createLifecycle(Collections.emptyMap(), dynamicConfig);
}
- private QueryLifecycle createLifecycle(Map<String, PerSegmentTimeoutConfig>
perSegmentTimeoutConfig)
+ private QueryLifecycle createLifecycle(Map<String, Object>
resolvedDefaultQueryContext, BrokerDynamicConfig dynamicConfig)
{
+
EasyMock.expect(conglomerate.getToolChest(EasyMock.anyObject())).andReturn(toolChest).once();
+ EasyMock.replay(conglomerate);
+
return new QueryLifecycle(
conglomerate,
texasRanger,
@@ -211,11 +187,9 @@ public class PerSegmentTimeoutInjectionTest
emitter,
requestLogger,
authzMapper,
- queryConfig,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
- Collections.emptyList(),
- perSegmentTimeoutConfig,
+ new QueryConfigSnapshot(resolvedDefaultQueryContext, dynamicConfig),
System.currentTimeMillis(),
System.nanoTime()
);
diff --git
a/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
b/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
index 5f8da0d21a3..3691f80ce27 100644
--- a/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
@@ -59,6 +59,8 @@ import org.apache.druid.query.policy.PolicyEnforcer;
import org.apache.druid.query.policy.RestrictAllTablesPolicyEnforcer;
import org.apache.druid.query.policy.RowFilterPolicy;
import org.apache.druid.query.timeseries.TimeseriesQuery;
+import org.apache.druid.server.broker.BrokerDynamicConfig;
+import org.apache.druid.server.broker.QueryConfigSnapshot;
import org.apache.druid.server.log.RequestLogger;
import org.apache.druid.server.security.Access;
import org.apache.druid.server.security.Action;
@@ -80,9 +82,7 @@ import org.junit.rules.ExpectedException;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -848,7 +848,6 @@ public class QueryLifecycleTest
replayAll();
// Create lifecycle with blocklist
- List<QueryBlocklistRule> queryBlocklist = ImmutableList.of(rule);
QueryLifecycle lifecycle = new QueryLifecycle(
conglomerate,
texasRanger,
@@ -856,11 +855,12 @@ public class QueryLifecycleTest
emitter,
requestLogger,
authzMapper,
- queryConfig,
authConfig,
policyEnforcer,
- queryBlocklist,
- Collections.emptyMap(),
+ new QueryConfigSnapshot(
+ ImmutableMap.of(),
+
BrokerDynamicConfig.builder().withQueryBlocklist(ImmutableList.of(rule)).build()
+ ),
System.currentTimeMillis(),
System.nanoTime()
);
@@ -900,7 +900,6 @@ public class QueryLifecycleTest
replayAll();
// Create lifecycle with blocklist
- List<QueryBlocklistRule> queryBlocklist = ImmutableList.of(rule);
QueryLifecycle lifecycle = new QueryLifecycle(
conglomerate,
texasRanger,
@@ -908,11 +907,12 @@ public class QueryLifecycleTest
emitter,
requestLogger,
authzMapper,
- queryConfig,
authConfig,
policyEnforcer,
- queryBlocklist,
- Collections.emptyMap(),
+ new QueryConfigSnapshot(
+ ImmutableMap.of(),
+
BrokerDynamicConfig.builder().withQueryBlocklist(ImmutableList.of(rule)).build()
+ ),
System.currentTimeMillis(),
System.nanoTime()
);
diff --git
a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
index 5d4ac54b99c..01c56548281 100644
--- a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
@@ -78,6 +78,7 @@ import org.apache.druid.query.policy.NoopPolicyEnforcer;
import org.apache.druid.query.policy.RowFilterPolicy;
import org.apache.druid.query.timeboundary.TimeBoundaryResultValue;
import org.apache.druid.server.broker.BrokerDynamicConfig;
+import org.apache.druid.server.broker.QueryConfigSnapshot;
import org.apache.druid.server.initialization.ServerConfig;
import org.apache.druid.server.log.TestRequestLogger;
import org.apache.druid.server.metrics.NoopServiceEmitter;
@@ -771,11 +772,9 @@ public class QueryResourceTest
emitter,
testRequestLogger,
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- overrideConfig,
new AuthConfig(),
NoopPolicyEnforcer.instance(),
- null,
- Collections.emptyMap(),
+ new QueryConfigSnapshot(overrideConfig.getContext(), null),
System.currentTimeMillis(),
System.nanoTime()
)
@@ -1961,10 +1960,12 @@ public class QueryResourceTest
private QueryResource createQueryResourceWithBlocklist(ServerConfig
serverConfig, QueryBlocklistRule... rules)
{
+ final BrokerDynamicConfig dynamicConfig =
+ new
BrokerDynamicConfig.Builder().withQueryBlocklist(Arrays.asList(rules)).build();
final BrokerViewOfBrokerConfig brokerViewOfBrokerConfig =
Mockito.mock(BrokerViewOfBrokerConfig.class);
- Mockito.when(brokerViewOfBrokerConfig.getDynamicConfig()).thenReturn(
- new
BrokerDynamicConfig.Builder().withQueryBlocklist(Arrays.asList(rules)).build()
- );
+
Mockito.when(brokerViewOfBrokerConfig.getDynamicConfig()).thenReturn(dynamicConfig);
+ Mockito.when(brokerViewOfBrokerConfig.snapshotForQuery())
+ .thenReturn(new QueryConfigSnapshot(Map.of(), dynamicConfig));
return createQueryResource(
new QueryLifecycleFactory(
diff --git
a/server/src/test/java/org/apache/druid/server/broker/BrokerDynamicConfigTest.java
b/server/src/test/java/org/apache/druid/server/broker/BrokerDynamicConfigTest.java
index 6a466bcd269..5d8e2b948d8 100644
---
a/server/src/test/java/org/apache/druid/server/broker/BrokerDynamicConfigTest.java
+++
b/server/src/test/java/org/apache/druid/server/broker/BrokerDynamicConfigTest.java
@@ -24,7 +24,11 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import nl.jqno.equalsverifier.EqualsVerifier;
+import org.apache.druid.java.util.common.Intervals;
+import org.apache.druid.query.Druids;
import org.apache.druid.query.QueryContext;
+import org.apache.druid.query.aggregation.CountAggregatorFactory;
+import org.apache.druid.query.timeseries.TimeseriesQuery;
import org.apache.druid.segment.TestHelper;
import org.apache.druid.server.DefaultQueryBlocklistRule;
import org.apache.druid.server.QueryBlocklistRule;
@@ -204,4 +208,45 @@ public class BrokerDynamicConfigTest
.usingGetClass()
.verify();
}
+
+ @Test
+ public void
testContextOverridesInjectsPerSegmentTimeoutForMatchingDatasource()
+ {
+ BrokerDynamicConfig config = perSegmentTimeout("ds", new
PerSegmentTimeoutConfig(5000, false));
+ Assert.assertEquals(5000L,
config.getContextOverridesForQuery(query("ds")).getPerSegmentTimeout());
+ }
+
+ @Test
+ public void testContextOverridesEmptyForMonitorOnly()
+ {
+ BrokerDynamicConfig config = perSegmentTimeout("ds", new
PerSegmentTimeoutConfig(5000, true));
+
Assert.assertTrue(config.getContextOverridesForQuery(query("ds")).isEmpty());
+ }
+
+ @Test
+ public void testContextOverridesEmptyForNonMatchingDatasource()
+ {
+ BrokerDynamicConfig config = perSegmentTimeout("other", new
PerSegmentTimeoutConfig(5000, false));
+
Assert.assertTrue(config.getContextOverridesForQuery(query("ds")).isEmpty());
+ }
+
+ @Test
+ public void testContextOverridesEmptyWhenNoPerSegmentTimeoutConfigured()
+ {
+
Assert.assertTrue(BrokerDynamicConfig.builder().build().getContextOverridesForQuery(query("ds")).isEmpty());
+ }
+
+ private static BrokerDynamicConfig perSegmentTimeout(String datasource,
PerSegmentTimeoutConfig timeoutConfig)
+ {
+ return
BrokerDynamicConfig.builder().withPerSegmentTimeoutConfig(Map.of(datasource,
timeoutConfig)).build();
+ }
+
+ private static TimeseriesQuery query(String datasource)
+ {
+ return Druids.newTimeseriesQueryBuilder()
+ .dataSource(datasource)
+ .intervals(List.of(Intervals.ETERNITY))
+ .aggregators(new CountAggregatorFactory("count"))
+ .build();
+ }
}
diff --git
a/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeQueryMaker.java
b/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeQueryMaker.java
index 8020b50f6c2..f0fb59254a0 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeQueryMaker.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/run/NativeQueryMaker.java
@@ -186,7 +186,9 @@ public class NativeQueryMaker implements QueryMaker
final QueryResponse<T> results = queryLifecycle.runSimple(
(Query<T>) query,
authenticationResult,
- authorizationResult
+ authorizationResult,
+ // The user-set keys, as distinct from the defaults SQL merged into
the context.
+ plannerContext.authContextKeys()
);
return mapResultSequence(
diff --git
a/web-console/src/dialogs/edit-context-dialog/query-context-completions.ts
b/web-console/src/dialogs/edit-context-dialog/query-context-completions.ts
index d7e67b7f492..68392e6a6dd 100644
--- a/web-console/src/dialogs/edit-context-dialog/query-context-completions.ts
+++ b/web-console/src/dialogs/edit-context-dialog/query-context-completions.ts
@@ -25,6 +25,10 @@ export const QUERY_CONTEXT_COMPLETIONS: JsonCompletionRule[]
= [
isObject: true,
completions: [
{ value: 'timeout', documentation: 'Query timeout in milliseconds' },
+ {
+ value: 'perSegmentTimeout',
+ documentation: 'Per-segment processing timeout in milliseconds',
+ },
{
value: 'priority',
documentation: 'Query priority (higher = more important)',
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]