This is an automated email from the ASF dual-hosted git repository.
tuglu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 71862170ac3 feat: add dynamic default query context (#19144)
71862170ac3 is described below
commit 71862170ac31386e05f68406b9aee7b32be8c86f
Author: jtuglu1 <[email protected]>
AuthorDate: Fri Mar 20 20:59:15 2026 -0700
feat: add dynamic default query context (#19144)
Add dynamic default query context to brokers. Allows operators to quickly
tune the default setting for query context values without requiring a
re-deployment or forcing upstream clients to update their queries.
---
docs/api-reference/dynamic-configuration-api.md | 35 +++-
docs/querying/query-context.md | 8 +-
.../server/EmbeddedBrokerDynamicConfigTest.java | 188 +++++++++++++++++++++
.../druid/grpc/server/GrpcEndpointInitializer.java | 7 +-
.../org/apache/druid/grpc/server/QueryDriver.java | 9 +-
.../java/org/apache/druid/grpc/BasicAuthTest.java | 3 +-
.../java/org/apache/druid/grpc/DriverTest.java | 3 +-
.../java/org/apache/druid/grpc/GrpcQueryTest.java | 3 +-
.../msq/dart/controller/sql/DartSqlEngine.java | 10 +-
.../druid/msq/dart/guice/DartControllerModule.java | 6 +
.../msq/sql/resources/SqlStatementResource.java | 10 +-
.../druid/msq/sql/resources/SqlTaskResource.java | 10 +-
.../org/apache/druid/query/DefaultQueryConfig.java | 3 +-
.../apache/druid/query/QueryConfigProvider.java | 34 ++++
.../druid/client/BaseBrokerViewOfConfig.java | 2 +-
.../druid/client/BrokerViewOfBrokerConfig.java | 47 +++++-
.../apache/druid/guice/QueryToolChestModule.java | 4 +
.../org/apache/druid/server/QueryLifecycle.java | 10 +-
.../apache/druid/server/QueryLifecycleFactory.java | 22 +--
.../druid/server/broker/BrokerDynamicConfig.java | 41 ++++-
.../druid/client/BrokerViewOfBrokerConfigTest.java | 33 +++-
.../metadata/SegmentMetadataCacheTestBase.java | 3 +-
.../apache/druid/server/QueryLifecycleTest.java | 11 +-
.../org/apache/druid/server/QueryResourceTest.java | 27 ++-
.../server/broker/BrokerDynamicConfigTest.java | 27 +++
.../main/java/org/apache/druid/cli/CliBroker.java | 6 +-
.../org/apache/druid/sql/avatica/DruidMeta.java | 18 +-
.../org/apache/druid/sql/http/SqlResource.java | 10 +-
.../druid/sql/avatica/DruidAvaticaHandlerTest.java | 2 +
.../sql/calcite/util/QueryFrameworkUtils.java | 3 +-
.../org/apache/druid/sql/guice/SqlModuleTest.java | 6 +-
.../apache/druid/sql/http/SqlHttpModuleTest.java | 2 +
.../broker-dynamic-config-completions.ts | 26 +++
.../broker-dynamic-config.tsx | 24 ++-
34 files changed, 539 insertions(+), 114 deletions(-)
diff --git a/docs/api-reference/dynamic-configuration-api.md
b/docs/api-reference/dynamic-configuration-api.md
index 1c37fa91e29..80433a54103 100644
--- a/docs/api-reference/dynamic-configuration-api.md
+++ b/docs/api-reference/dynamic-configuration-api.md
@@ -306,7 +306,13 @@ Host: http://ROUTER_IP:ROUTER_PORT
## Broker dynamic configuration
Broker dynamic configuration is managed through the Coordinator but consumed
by Brokers.
-These settings control broker behavior such as query blocking rules.
+These settings control broker behavior such as query blocking rules and
default query context values.
+
+> **Note:** Broker dynamic configuration is best-effort. Settings may not be
applied in certain
+> cases, such as when a Broker has recently started and hasn't received the
config yet, or if the
+> Broker cannot contact the Coordinator. Brokers poll the configuration
periodically (default every
+> 1 minute) and also receive push updates from the Coordinator for immediate
propagation. If a
+> setting is critical and must always be applied, use the equivalent static
runtime property instead.
### Get broker dynamic configuration
@@ -366,7 +372,11 @@ Host: http://ROUTER_IP:ROUTER_PORT
"dataSources": ["large_table"],
"queryTypes": ["scan"]
}
- ]
+ ],
+ "queryContext": {
+ "priority": 0,
+ "timeout": 300000
+ }
}
```
@@ -417,7 +427,7 @@ The endpoint supports a set of optional header parameters
to populate the audit
curl -X POST "http://ROUTER_IP:ROUTER_PORT/druid/coordinator/v1/broker/config"
\
-H "Content-Type: application/json" \
-H "X-Druid-Author: admin" \
--H "X-Druid-Comment: Add query blocklist rules" \
+-H "X-Druid-Comment: Add query blocklist rules and set default context" \
-d '{
"queryBlocklist": [
{
@@ -431,7 +441,11 @@ curl -X POST
"http://ROUTER_IP:ROUTER_PORT/druid/coordinator/v1/broker/config" \
"debug": "true"
}
}
- ]
+ ],
+ "queryContext": {
+ "priority": 0,
+ "timeout": 300000
+ }
}'
```
@@ -444,7 +458,7 @@ POST /druid/coordinator/v1/broker/config HTTP/1.1
Host: http://ROUTER_IP:ROUTER_PORT
Content-Type: application/json
X-Druid-Author: admin
-X-Druid-Comment: Add query blocklist rules
+X-Druid-Comment: Add query blocklist rules and set default context
{
"queryBlocklist": [
@@ -459,7 +473,11 @@ X-Druid-Comment: Add query blocklist rules
"debug": "true"
}
}
- ]
+ ],
+ "queryContext": {
+ "priority": 0,
+ "timeout": 300000
+ }
}
```
@@ -477,6 +495,7 @@ The following table shows the dynamic configuration
properties for the Broker.
|Property|Description|Default|
|--------|-----------|-------|
|`queryBlocklist`| List of rules to block queries based on datasource, query
type, and/or query context parameters. Each rule defines criteria that are
combined with AND logic. Blocked queries return an HTTP 403 error. See [Query
blocklist rules](#query-blocklist-rules) for details.|none|
+|`queryContext`| Map of default query context key-value pairs applied to all
queries on this broker. These values override static defaults set via runtime
properties (`druid.query.default.context.*`) but are overridden by context
values supplied in individual query payloads. Useful for setting cluster-wide
defaults such as `priority` or `timeout` without restarting. See [Query context
reference](../querying/query-context-reference.md) for available keys.|none|
#### Query blocklist rules
@@ -499,8 +518,6 @@ Each rule in the `queryBlocklist` array is a JSON object
with the following prop
- At least one criterion must be specified per rule to prevent accidentally
blocking all queries
- A query is blocked if it matches ANY rule in the blocklist (OR logic between
rules)
-> **Note:** Query blocking is best-effort. Queries may not be blocked in
certain cases, such as when a Broker has recently started and hasn't received
the config yet, or if the Broker cannot contact the Coordinator. Brokers poll
the configuration periodically (default every 1 minute) and also receive push
updates from the Coordinator for immediate propagation.
-
**Error response:**
When a query is blocked, the Broker returns an HTTP 403 error with a message
indicating the query ID and the rule that blocked it:
@@ -586,7 +603,7 @@ Host: http://ROUTER_IP:ROUTER_PORT
"comment": "Add query blocklist rules",
"ip": "127.0.0.1"
},
- "payload":
"{\"queryBlocklist\":[{\"ruleName\":\"block-expensive-scans\",\"dataSources\":[\"large_table\"],\"queryTypes\":[\"scan\"]}]}",
+ "payload":
"{\"queryBlocklist\":[{\"ruleName\":\"block-expensive-scans\",\"dataSources\":[\"large_table\"],\"queryTypes\":[\"scan\"]}],\"queryContext\":{\"priority\":0,\"timeout\":300000}}",
"auditTime": "2024-03-06T12:00:00.000Z"
}
]
diff --git a/docs/querying/query-context.md b/docs/querying/query-context.md
index 2f423945beb..076508a4df3 100644
--- a/docs/querying/query-context.md
+++ b/docs/querying/query-context.md
@@ -275,15 +275,17 @@ For more information, see [Configuration
reference](../configuration/index.md#ov
## Query context precedence
-For a given context query, Druid determines the final query context value to
use based on the following order of precedence, from lowest to highest:
+For a given query context, Druid determines the final query context value to
use based on the following order of precedence, from lowest to highest:
1. **Built-in defaults**: Druid uses the documented default values if you
don’t specify anything.
2. **Runtime properties**: If you configure parameters as
`druid.query.default.context.{PARAMETER}` in the configuration files, these
override the built-in defaults and act as your system-wide defaults.
-3. **Context object in HTTP request**: Parameters passed within the JSON
`context` object override both built-in defaults and runtime properties.
+3. **Broker Dynamic Config**: If you configure query context parameters in the
Broker's dynamic config (see [Broker Dynamic
Config](../api-reference/dynamic-configuration-api.md)). Overrides built-in
defaults and runtime properties.
-4. **SET statements**: Parameters set in Druid SQL using `SET key=value;` take
the highest precedence and override all other settings.
+4. **Context object in HTTP request**: Parameters passed within the JSON
`context` object override built-in defaults, runtime properties, and Broker
dynamic configs.
+
+5. **SET statements**: Parameters set in Druid SQL using `SET key=value;` take
the highest precedence and override all other settings.
## Learn more
diff --git
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/server/EmbeddedBrokerDynamicConfigTest.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/server/EmbeddedBrokerDynamicConfigTest.java
new file mode 100644
index 00000000000..34020229434
--- /dev/null
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/server/EmbeddedBrokerDynamicConfigTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.testing.embedded.server;
+
+import org.apache.druid.audit.AuditInfo;
+import org.apache.druid.common.config.JacksonConfigManager;
+import org.apache.druid.common.utils.IdUtils;
+import org.apache.druid.indexing.common.task.TaskBuilder;
+import org.apache.druid.query.QueryContext;
+import org.apache.druid.server.QueryBlocklistRule;
+import org.apache.druid.server.broker.BrokerDynamicConfig;
+import org.apache.druid.server.http.BrokerDynamicConfigSyncer;
+import org.apache.druid.testing.embedded.EmbeddedBroker;
+import org.apache.druid.testing.embedded.EmbeddedClusterApis;
+import org.apache.druid.testing.embedded.EmbeddedCoordinator;
+import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
+import org.apache.druid.testing.embedded.EmbeddedHistorical;
+import org.apache.druid.testing.embedded.EmbeddedIndexer;
+import org.apache.druid.testing.embedded.EmbeddedOverlord;
+import org.apache.druid.testing.embedded.indexing.Resources;
+import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Integration test for broker dynamic configuration
+ */
+public class EmbeddedBrokerDynamicConfigTest extends EmbeddedClusterTestBase
+{
+ // Fixed datasource ingested once for all tests; restored before each test
since the
+ // base class @BeforeEach would otherwise assign a fresh name.
+ private String fixedDataSource;
+
+ private final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
+ private final EmbeddedOverlord overlord = new EmbeddedOverlord();
+ private final EmbeddedIndexer indexer = new EmbeddedIndexer();
+ private final EmbeddedHistorical historical = new EmbeddedHistorical();
+ private final EmbeddedBroker broker = new EmbeddedBroker();
+
+ @Override
+ protected EmbeddedDruidCluster createCluster()
+ {
+ indexer.addProperty("druid.segment.handoff.pollDuration", "PT0.1s");
+
+ return EmbeddedDruidCluster.withEmbeddedDerbyAndZookeeper()
+ .useLatchableEmitter()
+ .addServer(overlord)
+ .addServer(coordinator)
+ .addServer(indexer)
+ .addServer(historical)
+ .addServer(broker);
+ }
+
+ @BeforeAll
+ @Override
+ public void setup() throws Exception
+ {
+ fixedDataSource = EmbeddedClusterApis.createTestDatasourceName();
+ dataSource = fixedDataSource;
+ super.setup();
+ ingestData();
+ cluster.callApi().waitForAllSegmentsToBeAvailable(fixedDataSource,
coordinator, broker);
+ }
+
+ @BeforeEach
+ @Override
+ protected void refreshDatasourceName()
+ {
+ dataSource = fixedDataSource;
+ }
+
+ @Test
+ @Timeout(30)
+ public void testQueryBlocklistBlocksMatchingQueries()
+ {
+ // Baseline: query succeeds before blocklist is applied
+ String initialResult = cluster.callApi().runSql("SELECT COUNT(*) FROM %s",
dataSource);
+ Assertions.assertFalse(initialResult.isBlank());
+
+ // Apply blocklist rule that matches all queries on this datasource
+ QueryBlocklistRule blockRule = new QueryBlocklistRule(
+ "block-test-datasource",
+ Set.of(dataSource),
+ null,
+ null
+ );
+ updateBrokerDynamicConfig(
+ BrokerDynamicConfig.builder()
+ .withQueryBlocklist(List.of(blockRule))
+ .build()
+ );
+
+ // Query should now throw due to FORBIDDEN blocklist rule
+ Assertions.assertThrows(
+ RuntimeException.class,
+ () -> cluster.callApi().runSql("SELECT COUNT(*) FROM %s", dataSource)
+ );
+
+ // Clear the blocklist and verify queries resume
+ updateBrokerDynamicConfig(BrokerDynamicConfig.builder().build());
+ String finalResult = cluster.callApi().runSql("SELECT COUNT(*) FROM %s",
dataSource);
+ Assertions.assertFalse(finalResult.isBlank());
+ }
+
+ @Test
+ @Timeout(30)
+ public void testDynamicQueryContextTimeoutCausesQueryToFail()
+ {
+ // Baseline: query succeeds before a timeout context is applied
+ String initialResult = cluster.callApi().runSql("SELECT COUNT(*) FROM %s",
dataSource);
+ Assertions.assertFalse(initialResult.isBlank());
+
+ // Apply a 1ms timeout via dynamic query context to force timeout
+ updateBrokerDynamicConfig(
+ BrokerDynamicConfig.builder()
+ .withQueryContext(QueryContext.of(Map.of("timeout",
1)))
+ .build()
+ );
+
+ // Query should now throw due to the timeout being exceeded
+ Assertions.assertThrows(
+ RuntimeException.class,
+ () -> cluster.callApi().runSql("SELECT COUNT(*) FROM %s", dataSource)
+ );
+
+ // Clear the dynamic context and verify queries resume
+ updateBrokerDynamicConfig(BrokerDynamicConfig.builder().build());
+ String finalResult = cluster.callApi().runSql("SELECT COUNT(*) FROM %s",
dataSource);
+ Assertions.assertFalse(finalResult.isBlank());
+ }
+
+ private void ingestData()
+ {
+ cluster.callApi().runTask(
+ TaskBuilder.ofTypeIndex()
+ .dataSource(dataSource)
+ .isoTimestampColumn("time")
+ .csvInputFormatWithColumns("time", "item", "value")
+ .inlineInputSourceWithData(Resources.InlineData.CSV_10_DAYS)
+ .segmentGranularity("DAY")
+ .dimensions()
+ .withId(IdUtils.getRandomId()),
+ overlord
+ );
+ }
+
+ /**
+ * Updates the broker dynamic config on the coordinator and synchronously
broadcasts it
+ * to all brokers.
+ *
+ * Uses {@link JacksonConfigManager} directly to avoid the HTTP endpoint's
builder-merge
+ * semantics, which cannot distinguish "clear to empty" from "not specified"
when fields
+ * are omitted via {@code @JsonInclude(NON_EMPTY)}.
+ */
+ private void updateBrokerDynamicConfig(BrokerDynamicConfig config)
+ {
+ coordinator.bindings()
+ .getInstance(JacksonConfigManager.class)
+ .set(BrokerDynamicConfig.CONFIG_KEY, config, new
AuditInfo("test", "[email protected]", "Testing", "127.0.0.1"));
+ coordinator.bindings()
+ .getInstance(BrokerDynamicConfigSyncer.class)
+ .broadcastConfigToBrokers();
+ }
+}
diff --git
a/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/GrpcEndpointInitializer.java
b/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/GrpcEndpointInitializer.java
index af80e3d928d..ed69719c2fd 100644
---
a/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/GrpcEndpointInitializer.java
+++
b/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/GrpcEndpointInitializer.java
@@ -27,14 +27,13 @@ import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.lifecycle.LifecycleStart;
import org.apache.druid.java.util.common.lifecycle.LifecycleStop;
import org.apache.druid.java.util.common.logger.Logger;
-import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.server.QueryLifecycleFactory;
import org.apache.druid.server.QueryScheduler;
import org.apache.druid.server.security.AuthenticatorMapper;
import org.apache.druid.sql.SqlStatementFactory;
import javax.inject.Inject;
-
import java.io.IOException;
/**
@@ -66,7 +65,7 @@ public class GrpcEndpointInitializer
final @Json ObjectMapper jsonMapper,
final @NativeQuery SqlStatementFactory sqlStatementFactory,
final QueryLifecycleFactory queryLifecycleFactory,
- final DefaultQueryConfig defaultQueryConfig,
+ final QueryConfigProvider queryConfigProvider,
final AuthenticatorMapper authMapper,
final QueryScheduler queryScheduler
)
@@ -76,7 +75,7 @@ public class GrpcEndpointInitializer
this.driver = new QueryDriver(
jsonMapper,
sqlStatementFactory,
- defaultQueryConfig.getContext(),
+ queryConfigProvider,
queryLifecycleFactory,
queryScheduler
);
diff --git
a/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/QueryDriver.java
b/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/QueryDriver.java
index c30b822648b..df457cf7d14 100644
---
a/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/QueryDriver.java
+++
b/extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/QueryDriver.java
@@ -41,6 +41,7 @@ import org.apache.druid.java.util.common.guava.Accumulator;
import org.apache.druid.java.util.common.guava.Sequence;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryInterruptedException;
import org.apache.druid.query.QueryToolChest;
import org.apache.druid.segment.column.ColumnHolder;
@@ -99,21 +100,21 @@ public class QueryDriver
private final ObjectMapper jsonMapper;
private final SqlStatementFactory sqlStatementFactory;
- private final Map<String, Object> defaultContext;
+ private final QueryConfigProvider queryConfigProvider;
private final QueryLifecycleFactory queryLifecycleFactory;
private final QueryScheduler queryScheduler;
public QueryDriver(
final ObjectMapper jsonMapper,
final SqlStatementFactory sqlStatementFactory,
- final Map<String, Object> defaultContext,
+ final QueryConfigProvider queryConfigProvider,
final QueryLifecycleFactory queryLifecycleFactory,
final QueryScheduler queryScheduler
)
{
this.jsonMapper = Preconditions.checkNotNull(jsonMapper, "jsonMapper");
this.sqlStatementFactory = Preconditions.checkNotNull(sqlStatementFactory,
"sqlStatementFactory");
- this.defaultContext = defaultContext;
+ this.queryConfigProvider = queryConfigProvider;
this.queryLifecycleFactory = queryLifecycleFactory;
this.queryScheduler = queryScheduler;
}
@@ -314,7 +315,7 @@ public class QueryDriver
{
return SqlQueryPlus.builder()
.sql(request.getQuery())
- .systemDefaultContext(defaultContext)
+ .systemDefaultContext(queryConfigProvider.getContext())
.queryContext(translateContext(request))
.sqlParameters(translateParameters(request))
.auth(authResult)
diff --git
a/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/BasicAuthTest.java
b/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/BasicAuthTest.java
index f04af5370a3..167bef17f54 100644
---
a/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/BasicAuthTest.java
+++
b/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/BasicAuthTest.java
@@ -31,6 +31,7 @@ import org.apache.druid.grpc.server.GrpcQueryConfig;
import org.apache.druid.grpc.server.QueryDriver;
import org.apache.druid.grpc.server.QueryServer;
import org.apache.druid.metadata.DefaultPasswordProvider;
+import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.security.basic.authentication.BasicHTTPAuthenticator;
import
org.apache.druid.security.basic.authentication.validator.CredentialsValidator;
import org.apache.druid.server.QueryStackTests;
@@ -72,7 +73,7 @@ public class BasicAuthTest extends BaseCalciteQueryTest
QueryDriver driver = new QueryDriver(
sqlTestFramework.queryJsonMapper(),
plannerFixture.statementFactory(),
- Map.of("forbiddenKey", "system-default-value"), // systen default
forbidden key, only superuser can change it
+ new DefaultQueryConfig(Map.of("forbiddenKey",
"system-default-value")), // system default forbidden key, only superuser can
change it
sqlTestFramework.queryLifecycleFactory(),
QueryStackTests.DEFAULT_NOOP_SCHEDULER
);
diff --git
a/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/DriverTest.java
b/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/DriverTest.java
index 0bf7aa2c0e3..8e2e99406a9 100644
---
a/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/DriverTest.java
+++
b/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/DriverTest.java
@@ -27,6 +27,7 @@ import
org.apache.druid.grpc.proto.QueryOuterClass.QueryResponse;
import org.apache.druid.grpc.proto.QueryOuterClass.QueryResultFormat;
import org.apache.druid.grpc.proto.QueryOuterClass.QueryStatus;
import org.apache.druid.grpc.server.QueryDriver;
+import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.server.QueryStackTests;
import org.apache.druid.server.security.AuthConfig;
import org.apache.druid.sql.calcite.BaseCalciteQueryTest;
@@ -58,7 +59,7 @@ public class DriverTest extends BaseCalciteQueryTest
driver = new QueryDriver(
sqlTestFramework.queryJsonMapper(),
plannerFixture.statementFactory(),
- Map.of(),
+ new DefaultQueryConfig(Map.of()),
sqlTestFramework.queryLifecycleFactory(),
QueryStackTests.DEFAULT_NOOP_SCHEDULER
);
diff --git
a/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/GrpcQueryTest.java
b/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/GrpcQueryTest.java
index 06cbbdd6afe..46c2df5a7b5 100644
---
a/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/GrpcQueryTest.java
+++
b/extensions-contrib/grpc-query/src/test/java/org/apache/druid/grpc/GrpcQueryTest.java
@@ -34,6 +34,7 @@ import org.apache.druid.grpc.proto.TestResults.QueryResult;
import org.apache.druid.grpc.server.GrpcQueryConfig;
import org.apache.druid.grpc.server.QueryDriver;
import org.apache.druid.grpc.server.QueryServer;
+import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.server.QueryStackTests;
import org.apache.druid.server.security.AllowAllAuthenticator;
import org.apache.druid.server.security.AuthConfig;
@@ -77,7 +78,7 @@ public class GrpcQueryTest extends BaseCalciteQueryTest
QueryDriver driver = new QueryDriver(
sqlTestFramework.queryJsonMapper(),
plannerFixture.statementFactory(),
- Map.of(),
+ new DefaultQueryConfig(Map.of()),
sqlTestFramework.queryLifecycleFactory(),
QueryStackTests.DEFAULT_NOOP_SCHEDULER
);
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/dart/controller/sql/DartSqlEngine.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/dart/controller/sql/DartSqlEngine.java
index 2410ea8486d..82a2479a392 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/dart/controller/sql/DartSqlEngine.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/dart/controller/sql/DartSqlEngine.java
@@ -46,7 +46,7 @@ import org.apache.druid.msq.indexing.error.CancellationReason;
import org.apache.druid.msq.querykit.MultiQueryKit;
import org.apache.druid.msq.sql.DartQueryKitSpecFactory;
import org.apache.druid.msq.sql.MSQTaskSqlEngine;
-import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContext;
import org.apache.druid.query.QueryContexts;
import org.apache.druid.server.QueryScheduler;
@@ -87,7 +87,7 @@ public class DartSqlEngine implements SqlEngine
private final ServerConfig serverConfig;
private final QueryKitSpecFactory queryKitSpecFactory;
private final MultiQueryKit queryKit;
- private final DefaultQueryConfig dartQueryConfig;
+ private final QueryConfigProvider queryConfigProvider;
private final SqlToolbox toolbox;
private final DartSqlClients sqlClients;
@@ -100,7 +100,7 @@ public class DartSqlEngine implements SqlEngine
DartQueryKitSpecFactory queryKitSpecFactory,
MultiQueryKit queryKit,
ServerConfig serverConfig,
- @Dart DefaultQueryConfig dartQueryConfig,
+ @Dart QueryConfigProvider queryConfigProvider,
SqlToolbox toolbox,
DartSqlClients sqlClients
)
@@ -112,7 +112,7 @@ public class DartSqlEngine implements SqlEngine
this.queryKitSpecFactory = queryKitSpecFactory;
this.queryKit = queryKit;
this.serverConfig = serverConfig;
- this.dartQueryConfig = dartQueryConfig;
+ this.queryConfigProvider = queryConfigProvider;
this.toolbox = toolbox;
this.sqlClients = sqlClients;
}
@@ -223,7 +223,7 @@ public class DartSqlEngine implements SqlEngine
public void initContextMap(Map<String, Object> contextMap)
{
// Default context keys from dartQueryConfig.
- for (Map.Entry<String, Object> entry :
dartQueryConfig.getContext().entrySet()) {
+ for (Map.Entry<String, Object> entry :
queryConfigProvider.getContext().entrySet()) {
contextMap.putIfAbsent(entry.getKey(), entry.getValue());
}
/**
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/dart/guice/DartControllerModule.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/dart/guice/DartControllerModule.java
index 8219baa03aa..3b86b5e8b70 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/dart/guice/DartControllerModule.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/dart/guice/DartControllerModule.java
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Binder;
import com.google.inject.Inject;
+import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.multibindings.Multibinder;
@@ -52,6 +53,7 @@ import
org.apache.druid.msq.dart.controller.sql.DartSqlClients;
import org.apache.druid.msq.dart.controller.sql.DartSqlEngine;
import org.apache.druid.msq.rpc.ResourcePermissionMapper;
import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.sql.SqlStatementFactory;
import org.apache.druid.sql.SqlToolbox;
import org.apache.druid.sql.calcite.run.SqlEngine;
@@ -84,6 +86,10 @@ public class DartControllerModule implements DruidModule
{
JsonConfigProvider.bind(binder, DartModules.DART_PROPERTY_BASE +
".controller", DartControllerConfig.class);
JsonConfigProvider.bind(binder, DartModules.DART_PROPERTY_BASE +
".query", DefaultQueryConfig.class, Dart.class);
+ // Dart uses its own static DefaultQueryConfig rather than
BrokerViewOfBrokerConfig because
+ // DartSqlEngine.initContextMap() manages context merging independently
for Dart queries.
+ binder.bind(Key.get(QueryConfigProvider.class, Dart.class))
+ .to(Key.get(DefaultQueryConfig.class, Dart.class));
LifecycleModule.register(binder, DartSqlClients.class);
LifecycleModule.register(binder, DartMessageRelays.class);
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
index d3363b25498..06adbc74f19 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
@@ -69,8 +69,8 @@ import org.apache.druid.msq.sql.entity.ResultSetInformation;
import org.apache.druid.msq.sql.entity.SqlStatementResult;
import org.apache.druid.msq.util.MultiStageQueryContext;
import org.apache.druid.msq.util.SqlStatementResourceHelper;
-import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.ExecutionMode;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContext;
import org.apache.druid.query.QueryContexts;
import org.apache.druid.query.QueryException;
@@ -136,7 +136,7 @@ public class SqlStatementResource
private final OverlordClient overlordClient;
private final StorageConnector storageConnector;
private final AuthorizerMapper authorizerMapper;
- private final DefaultQueryConfig defaultQueryConfig;
+ private final QueryConfigProvider queryConfigProvider;
private final ServerConfig serverConfig;
private final WireTransferableContext wireTransferableContext;
@@ -147,7 +147,7 @@ public class SqlStatementResource
final OverlordClient overlordClient,
final @MultiStageQuery StorageConnectorProvider storageConnectorProvider,
final AuthorizerMapper authorizerMapper,
- final DefaultQueryConfig defaultQueryConfig,
+ final QueryConfigProvider queryConfigProvider,
final ServerConfig serverConfig,
final WireTransferableContext wireTransferableContext
)
@@ -157,7 +157,7 @@ public class SqlStatementResource
this.overlordClient = overlordClient;
this.storageConnector =
storageConnectorProvider.createStorageConnector(null);
this.authorizerMapper = authorizerMapper;
- this.defaultQueryConfig = defaultQueryConfig;
+ this.queryConfigProvider = queryConfigProvider;
this.serverConfig = serverConfig;
this.wireTransferableContext = wireTransferableContext;
}
@@ -202,7 +202,7 @@ public class SqlStatementResource
sqlQuery,
req,
ImmutableMap.<String, Object>builder()
- .putAll(defaultQueryConfig.getContext())
+ .putAll(queryConfigProvider.getContext())
.put(RESULT_FORMAT, sqlQuery.getResultFormat())
.build()
);
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
index 75c064b30bf..37714b6f906 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
@@ -33,7 +33,7 @@ import org.apache.druid.java.util.common.guava.Yielders;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.msq.guice.MultiStageQuery;
import org.apache.druid.msq.sql.MSQTaskSqlEngine;
-import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryException;
import org.apache.druid.query.http.SqlTaskStatus;
import org.apache.druid.server.QueryResponse;
@@ -81,20 +81,20 @@ public class SqlTaskResource
private static final Logger log = new Logger(SqlTaskResource.class);
private final SqlStatementFactory sqlStatementFactory;
- private final DefaultQueryConfig defaultQueryConfig;
+ private final QueryConfigProvider queryConfigProvider;
private final ObjectMapper jsonMapper;
private final ServerConfig serverConfig;
@Inject
public SqlTaskResource(
final @MultiStageQuery SqlStatementFactory sqlStatementFactory,
- final DefaultQueryConfig defaultQueryConfig,
+ final QueryConfigProvider queryConfigProvider,
final ObjectMapper jsonMapper,
final ServerConfig serverConfig
)
{
this.sqlStatementFactory = sqlStatementFactory;
- this.defaultQueryConfig = defaultQueryConfig;
+ this.queryConfigProvider = queryConfigProvider;
this.jsonMapper = jsonMapper;
this.serverConfig = serverConfig;
}
@@ -131,7 +131,7 @@ public class SqlTaskResource
final SqlQueryPlus sqlQueryPlus;
final HttpStatement stmt;
try {
- sqlQueryPlus = SqlResource.makeSqlQueryPlus(sqlQuery, req,
defaultQueryConfig.getContext());
+ sqlQueryPlus = SqlResource.makeSqlQueryPlus(sqlQuery, req,
queryConfigProvider.getContext());
stmt = sqlStatementFactory.httpStatement(sqlQueryPlus, req);
}
catch (Exception e) {
diff --git
a/processing/src/main/java/org/apache/druid/query/DefaultQueryConfig.java
b/processing/src/main/java/org/apache/druid/query/DefaultQueryConfig.java
index d831fa2d840..0924c5017d0 100644
--- a/processing/src/main/java/org/apache/druid/query/DefaultQueryConfig.java
+++ b/processing/src/main/java/org/apache/druid/query/DefaultQueryConfig.java
@@ -37,7 +37,7 @@ import java.util.Map;
* @see org.apache.druid.query.scan.ScanQueryConfig
*
*/
-public class DefaultQueryConfig
+public class DefaultQueryConfig implements QueryConfigProvider
{
/**
* Config that does nothing.
@@ -54,6 +54,7 @@ public class DefaultQueryConfig
@JsonProperty
private final Map<String, Object> context;
+ @Override
@Nonnull
public Map<String, Object> getContext()
{
diff --git
a/processing/src/main/java/org/apache/druid/query/QueryConfigProvider.java
b/processing/src/main/java/org/apache/druid/query/QueryConfigProvider.java
new file mode 100644
index 00000000000..6b533ab6a37
--- /dev/null
+++ b/processing/src/main/java/org/apache/druid/query/QueryConfigProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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.query;
+
+import java.util.Map;
+
+/**
+ * Provides the default query config 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
+ * operator-supplied overrides pushed dynamically from the Coordinator.
+ */
+public interface QueryConfigProvider
+{
+ Map<String, Object> getContext();
+}
diff --git
a/server/src/main/java/org/apache/druid/client/BaseBrokerViewOfConfig.java
b/server/src/main/java/org/apache/druid/client/BaseBrokerViewOfConfig.java
index ca483d1d46f..2f733f1a82d 100644
--- a/server/src/main/java/org/apache/druid/client/BaseBrokerViewOfConfig.java
+++ b/server/src/main/java/org/apache/druid/client/BaseBrokerViewOfConfig.java
@@ -71,7 +71,7 @@ public abstract class BaseBrokerViewOfConfig<DynamicConfig>
public synchronized void setDynamicConfig(@NotNull DynamicConfig
updatedConfig)
{
config = updatedConfig;
- log.info("Updated %s to [%s]", getConfigTypeName(), updatedConfig);
+ log.info("Updated [%s] dynamic config to [%s]", getConfigTypeName(),
updatedConfig);
}
/**
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 c85f09e828b..b67b4bc46da 100644
--- a/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
+++ b/server/src/main/java/org/apache/druid/client/BrokerViewOfBrokerConfig.java
@@ -28,27 +28,48 @@ import
org.apache.druid.client.coordinator.CoordinatorClientImpl;
import org.apache.druid.discovery.NodeRole;
import org.apache.druid.guice.annotations.EscalatedGlobal;
import org.apache.druid.guice.annotations.Json;
+import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
+import org.apache.druid.query.QueryContext;
+import org.apache.druid.query.QueryContexts;
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 javax.validation.constraints.NotNull;
+import java.util.Map;
/**
* Broker view of broker dynamic configuration.
+ *
+ * <p>Also implements {@link QueryConfigProvider} to expose the effective
default query context: the
+ * merge of static defaults (from {@link DefaultQueryConfig}) and
operator-supplied overrides
+ * (from {@link BrokerDynamicConfig#getQueryContext()}). Dynamic values take
precedence.
*/
public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynamicConfig>
+ implements QueryConfigProvider
{
private final CoordinatorClient coordinatorClient;
+ private final DefaultQueryConfig defaultQueryConfig;
+
+ /**
+ * Pre-computed merge of {@link DefaultQueryConfig#getContext()} and
+ * {@link BrokerDynamicConfig#getQueryContext()}, recomputed on each config
sync.
+ * Dynamic config values override static defaults. {@link QueryContext}
provides immutability.
+ */
+ private volatile QueryContext resolvedDefaultQueryContext;
@Inject
public BrokerViewOfBrokerConfig(
@Json final ObjectMapper jsonMapper,
@EscalatedGlobal final ServiceClientFactory clientFactory,
- @Coordinator final ServiceLocator serviceLocator
+ @Coordinator final ServiceLocator serviceLocator,
+ final DefaultQueryConfig defaultQueryConfig
)
{
+ this.defaultQueryConfig = defaultQueryConfig;
+ this.resolvedDefaultQueryContext =
QueryContext.of(defaultQueryConfig.getContext());
this.coordinatorClient =
new CoordinatorClientImpl(
clientFactory.makeClient(
@@ -61,9 +82,14 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
}
@VisibleForTesting
- public BrokerViewOfBrokerConfig(CoordinatorClient coordinatorClient)
+ public BrokerViewOfBrokerConfig(
+ final CoordinatorClient coordinatorClient,
+ final DefaultQueryConfig defaultQueryConfig
+ )
{
this.coordinatorClient = coordinatorClient;
+ this.defaultQueryConfig = defaultQueryConfig;
+ this.resolvedDefaultQueryContext =
QueryContext.of(defaultQueryConfig.getContext());
}
@Override
@@ -79,11 +105,26 @@ public class BrokerViewOfBrokerConfig extends
BaseBrokerViewOfConfig<BrokerDynam
}
/**
- * Update the config view with a new broker dynamic config snapshot.
+ * Update the config view with a new broker dynamic config snapshot, and
recompute the
+ * resolved default query context by merging static defaults with dynamic
overrides.
*/
@Override
public synchronized void setDynamicConfig(@NotNull BrokerDynamicConfig
updatedConfig)
{
super.setDynamicConfig(updatedConfig);
+ resolvedDefaultQueryContext = QueryContext.of(QueryContexts.override(
+ defaultQueryConfig.getContext(),
+ updatedConfig.getQueryContext().asMap()
+ ));
+ }
+
+ /**
+ * Returns the pre-computed merge of static {@link DefaultQueryConfig}
context and dynamic
+ * {@link BrokerDynamicConfig#getQueryContext()}. Dynamic values take
precedence over static defaults.
+ */
+ @Override
+ public Map<String, Object> getContext()
+ {
+ return resolvedDefaultQueryContext.asMap();
}
}
diff --git
a/server/src/main/java/org/apache/druid/guice/QueryToolChestModule.java
b/server/src/main/java/org/apache/druid/guice/QueryToolChestModule.java
index c8466f887d7..72f0e142b29 100644
--- a/server/src/main/java/org/apache/druid/guice/QueryToolChestModule.java
+++ b/server/src/main/java/org/apache/druid/guice/QueryToolChestModule.java
@@ -28,6 +28,7 @@ import
org.apache.druid.query.DefaultGenericQueryMetricsFactory;
import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.GenericQueryMetricsFactory;
import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryToolChest;
import org.apache.druid.query.QueryToolChestWarehouse;
import org.apache.druid.query.datasourcemetadata.DataSourceMetadataQuery;
@@ -61,6 +62,7 @@ import org.apache.druid.query.topn.TopNQuery;
import org.apache.druid.query.topn.TopNQueryConfig;
import org.apache.druid.query.topn.TopNQueryMetricsFactory;
import org.apache.druid.query.topn.TopNQueryQueryToolChest;
+
import java.util.Map;
/**
@@ -99,6 +101,8 @@ public class QueryToolChestModule implements Module
binder.bind(QueryToolChestWarehouse.class).to(ConglomerateBackedToolChestWarehouse.class);
JsonConfigProvider.bind(binder, "druid.query.default",
DefaultQueryConfig.class);
+ // DefaultQueryContext defaults to the static DefaultQueryConfig; brokers
override this binding.
+ binder.bind(QueryConfigProvider.class).to(DefaultQueryConfig.class);
JsonConfigProvider.bind(binder, "druid.query.groupBy",
GroupByQueryConfig.class);
JsonConfigProvider.bind(binder, "druid.query.search",
SearchQueryConfig.class);
JsonConfigProvider.bind(binder, "druid.query.topN", TopNQueryConfig.class);
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 6add90ace1f..def4bb5b831 100644
--- a/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
+++ b/server/src/main/java/org/apache/druid/server/QueryLifecycle.java
@@ -34,10 +34,10 @@ import org.apache.druid.java.util.common.guava.Sequences;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.query.BaseQuery;
-import org.apache.druid.query.DefaultQueryConfig;
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;
@@ -97,7 +97,7 @@ public class QueryLifecycle
private final ServiceEmitter emitter;
private final RequestLogger requestLogger;
private final AuthorizerMapper authorizerMapper;
- private final DefaultQueryConfig defaultQueryConfig;
+ private final QueryConfigProvider queryConfigProvider;
private final AuthConfig authConfig;
private final PolicyEnforcer policyEnforcer;
private final List<QueryBlocklistRule> queryBlocklist;
@@ -120,7 +120,7 @@ public class QueryLifecycle
final ServiceEmitter emitter,
final RequestLogger requestLogger,
final AuthorizerMapper authorizerMapper,
- final DefaultQueryConfig defaultQueryConfig,
+ final QueryConfigProvider queryConfigProvider,
final AuthConfig authConfig,
final PolicyEnforcer policyEnforcer,
final List<QueryBlocklistRule> queryBlocklist,
@@ -134,7 +134,7 @@ public class QueryLifecycle
this.emitter = emitter;
this.requestLogger = requestLogger;
this.authorizerMapper = authorizerMapper;
- this.defaultQueryConfig = defaultQueryConfig;
+ this.queryConfigProvider = queryConfigProvider;
this.authConfig = authConfig;
this.policyEnforcer = policyEnforcer;
this.queryBlocklist = queryBlocklist;
@@ -217,7 +217,7 @@ public class QueryLifecycle
}
Map<String, Object> mergedUserAndConfigContext = QueryContexts.override(
- defaultQueryConfig.getContext(),
+ queryConfigProvider.getContext(),
baseQuery.getContext()
);
mergedUserAndConfigContext.put(BaseQuery.QUERY_ID, queryId);
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 66d9ed4f629..b32a5aafffb 100644
--- a/server/src/main/java/org/apache/druid/server/QueryLifecycleFactory.java
+++ b/server/src/main/java/org/apache/druid/server/QueryLifecycleFactory.java
@@ -19,13 +19,12 @@
package org.apache.druid.server;
-import com.google.common.base.Supplier;
import com.google.inject.Inject;
import org.apache.druid.client.BrokerViewOfBrokerConfig;
import org.apache.druid.guice.LazySingleton;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
-import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.GenericQueryMetricsFactory;
+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;
@@ -46,7 +45,7 @@ public class QueryLifecycleFactory
private final ServiceEmitter emitter;
private final RequestLogger requestLogger;
private final AuthorizerMapper authorizerMapper;
- private final DefaultQueryConfig defaultQueryConfig;
+ private final QueryConfigProvider queryConfigProvider;
private final AuthConfig authConfig;
private final PolicyEnforcer policyEnforcer;
private final BrokerViewOfBrokerConfig brokerViewOfBrokerConfig;
@@ -61,7 +60,7 @@ public class QueryLifecycleFactory
final AuthConfig authConfig,
final PolicyEnforcer policyEnforcer,
final AuthorizerMapper authorizerMapper,
- final Supplier<DefaultQueryConfig> queryConfigSupplier,
+ final QueryConfigProvider queryConfigProvider,
@Nullable final BrokerViewOfBrokerConfig brokerViewOfBrokerConfig
)
{
@@ -71,7 +70,7 @@ public class QueryLifecycleFactory
this.emitter = emitter;
this.requestLogger = requestLogger;
this.authorizerMapper = authorizerMapper;
- this.defaultQueryConfig = queryConfigSupplier.get();
+ this.queryConfigProvider = queryConfigProvider;
this.authConfig = authConfig;
this.policyEnforcer = policyEnforcer;
this.brokerViewOfBrokerConfig = brokerViewOfBrokerConfig;
@@ -79,13 +78,10 @@ public class QueryLifecycleFactory
public QueryLifecycle factorize()
{
- // Extract query blocklist from broker config, or use empty list if not on
broker
- final List<QueryBlocklistRule> queryBlocklist;
- if (brokerViewOfBrokerConfig != null &&
brokerViewOfBrokerConfig.getDynamicConfig() != null) {
- queryBlocklist =
brokerViewOfBrokerConfig.getDynamicConfig().getQueryBlocklist();
- } else {
- queryBlocklist = Collections.emptyList();
- }
+ final List<QueryBlocklistRule> queryBlocklist =
+ brokerViewOfBrokerConfig != null &&
brokerViewOfBrokerConfig.getDynamicConfig() != null
+ ? brokerViewOfBrokerConfig.getDynamicConfig().getQueryBlocklist()
+ : Collections.emptyList();
return new QueryLifecycle(
conglomerate,
@@ -94,7 +90,7 @@ public class QueryLifecycleFactory
emitter,
requestLogger,
authorizerMapper,
- defaultQueryConfig,
+ queryConfigProvider,
authConfig,
policyEnforcer,
queryBlocklist,
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 e5156bbb889..45c646be8a7 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
@@ -20,9 +20,9 @@
package org.apache.druid.server.broker;
import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.druid.common.config.Configs;
+import org.apache.druid.query.QueryContext;
import org.apache.druid.server.QueryBlocklistRule;
import javax.annotation.Nullable;
@@ -46,21 +46,34 @@ public class BrokerDynamicConfig
*/
private final List<QueryBlocklistRule> queryBlocklist;
+ /**
+ * Default query context values set dynamically by operators. These override
static defaults from
+ * {@link org.apache.druid.query.DefaultQueryConfig} but are overridden by
per-query context.
+ */
+ private final QueryContext queryContext;
+
@JsonCreator
public BrokerDynamicConfig(
- @JsonProperty("queryBlocklist") @Nullable List<QueryBlocklistRule>
queryBlocklist
+ @JsonProperty("queryBlocklist") @Nullable List<QueryBlocklistRule>
queryBlocklist,
+ @JsonProperty("queryContext") @Nullable QueryContext queryContext
)
{
this.queryBlocklist = Configs.valueOrDefault(queryBlocklist,
Collections.emptyList());
+ this.queryContext = Configs.valueOrDefault(queryContext,
QueryContext.empty());
}
@JsonProperty
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
public List<QueryBlocklistRule> getQueryBlocklist()
{
return queryBlocklist;
}
+ @JsonProperty
+ public QueryContext getQueryContext()
+ {
+ return queryContext;
+ }
+
@Override
public boolean equals(Object o)
{
@@ -71,13 +84,14 @@ public class BrokerDynamicConfig
return false;
}
BrokerDynamicConfig that = (BrokerDynamicConfig) o;
- return Objects.equals(queryBlocklist, that.queryBlocklist);
+ return Objects.equals(queryBlocklist, that.queryBlocklist)
+ && Objects.equals(queryContext, that.queryContext);
}
@Override
public int hashCode()
{
- return Objects.hash(queryBlocklist);
+ return Objects.hash(queryBlocklist, queryContext);
}
@Override
@@ -85,6 +99,7 @@ public class BrokerDynamicConfig
{
return "BrokerDynamicConfig{" +
"queryBlocklist=" + queryBlocklist +
+ ", queryContext=" + queryContext +
'}';
}
@@ -96,6 +111,7 @@ public class BrokerDynamicConfig
public static class Builder
{
private List<QueryBlocklistRule> queryBlocklist;
+ private QueryContext queryContext;
public Builder()
{
@@ -103,10 +119,12 @@ public class BrokerDynamicConfig
@JsonCreator
public Builder(
- @JsonProperty("queryBlocklist") @Nullable List<QueryBlocklistRule>
queryBlocklist
+ @JsonProperty("queryBlocklist") @Nullable List<QueryBlocklistRule>
queryBlocklist,
+ @JsonProperty("queryContext") @Nullable QueryContext queryContext
)
{
this.queryBlocklist = queryBlocklist;
+ this.queryContext = queryContext;
}
public Builder withQueryBlocklist(List<QueryBlocklistRule> queryBlocklist)
@@ -115,9 +133,15 @@ public class BrokerDynamicConfig
return this;
}
+ public Builder withQueryContext(QueryContext queryContext)
+ {
+ this.queryContext = queryContext;
+ return this;
+ }
+
public BrokerDynamicConfig build()
{
- return new BrokerDynamicConfig(queryBlocklist);
+ return new BrokerDynamicConfig(queryBlocklist, queryContext);
}
/**
@@ -127,7 +151,8 @@ public class BrokerDynamicConfig
public BrokerDynamicConfig build(@Nullable BrokerDynamicConfig defaults)
{
return new BrokerDynamicConfig(
- Configs.valueOrDefault(queryBlocklist, defaults != null ?
defaults.getQueryBlocklist() : null)
+ Configs.valueOrDefault(queryBlocklist, defaults != null ?
defaults.getQueryBlocklist() : null),
+ Configs.valueOrDefault(queryContext, defaults != null ?
defaults.getQueryContext() : null)
);
}
}
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 a685abadafa..c4877220ffc 100644
---
a/server/src/test/java/org/apache/druid/client/BrokerViewOfBrokerConfigTest.java
+++
b/server/src/test/java/org/apache/druid/client/BrokerViewOfBrokerConfigTest.java
@@ -19,29 +19,36 @@
package org.apache.druid.client;
+import com.google.common.collect.ImmutableMap;
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.broker.BrokerDynamicConfig;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
+import java.util.Map;
+
public class BrokerViewOfBrokerConfigTest
{
private BrokerViewOfBrokerConfig target;
private CoordinatorClient coordinatorClient;
private BrokerDynamicConfig config;
+ private DefaultQueryConfig defaultQueryConfig;
@Before
public void setUp() throws Exception
{
config = BrokerDynamicConfig.builder().build();
+ defaultQueryConfig = new DefaultQueryConfig(ImmutableMap.of("timeout",
30000, "useCache", true));
coordinatorClient = Mockito.mock(CoordinatorClient.class);
Mockito.when(coordinatorClient.getBrokerDynamicConfig()).thenReturn(Futures.immediateFuture(config));
- target = new BrokerViewOfBrokerConfig(coordinatorClient);
+ target = new BrokerViewOfBrokerConfig(coordinatorClient,
defaultQueryConfig);
}
@Test
@@ -51,4 +58,28 @@ public class BrokerViewOfBrokerConfigTest
Mockito.verify(coordinatorClient,
Mockito.times(1)).getBrokerDynamicConfig();
Assert.assertEquals(config, target.getDynamicConfig());
}
+
+ @Test
+ public void testResolvedContextMergesDynamicOverStaticDefaults()
+ {
+ // Dynamic config overrides "useCache" and adds "priority"; static
"timeout" is preserved.
+ final BrokerDynamicConfig dynamicConfig = BrokerDynamicConfig.builder()
+
.withQueryContext(
+
QueryContext.of(ImmutableMap.of("useCache", false, "priority", 5))
+ )
+ .build();
+ target.setDynamicConfig(dynamicConfig);
+
+ final Map<String, Object> resolved = target.getContext();
+ Assert.assertEquals(30000, resolved.get("timeout"));
+ Assert.assertEquals(false, resolved.get("useCache"));
+ Assert.assertEquals(5, resolved.get("priority"));
+ }
+
+ @Test
+ public void
testResolvedContextEqualsStaticDefaultsWhenDynamicContextIsEmpty()
+ {
+ target.setDynamicConfig(BrokerDynamicConfig.builder().build());
+ Assert.assertEquals(defaultQueryConfig.getContext(), target.getContext());
+ }
}
diff --git
a/server/src/test/java/org/apache/druid/segment/metadata/SegmentMetadataCacheTestBase.java
b/server/src/test/java/org/apache/druid/segment/metadata/SegmentMetadataCacheTestBase.java
index d36d4a7c191..10ec3992b97 100644
---
a/server/src/test/java/org/apache/druid/segment/metadata/SegmentMetadataCacheTestBase.java
+++
b/server/src/test/java/org/apache/druid/segment/metadata/SegmentMetadataCacheTestBase.java
@@ -19,7 +19,6 @@
package org.apache.druid.segment.metadata;
-import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.data.input.InputRow;
@@ -292,7 +291,7 @@ public abstract class SegmentMetadataCacheTestBase extends
InitializedNullHandli
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
);
}
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 1f21c065b18..b478ab9befc 100644
--- a/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
@@ -19,7 +19,6 @@
package org.apache.druid.server;
-import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -37,10 +36,10 @@ import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.java.util.common.guava.Sequences;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.query.DataSource;
-import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.Druids;
import org.apache.druid.query.GenericQueryMetricsFactory;
import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContextTest;
import org.apache.druid.query.QueryMetrics;
import org.apache.druid.query.QueryRunner;
@@ -118,9 +117,9 @@ public class QueryLifecycleTest
@Bind
AuthorizerMapper authzMapper;
- DefaultQueryConfig queryConfig;
+ QueryConfigProvider queryConfig;
@Bind(lazy = true)
- Supplier<DefaultQueryConfig> queryConfigSupplier;
+ QueryConfigProvider queryConfigProvider;
@Bind(lazy = true)
AuthConfig authConfig;
@@ -151,8 +150,8 @@ public class QueryLifecycleTest
requestLogger = EasyMock.createNiceMock(RequestLogger.class);
authorizer = EasyMock.createMock(Authorizer.class);
authzMapper = new AuthorizerMapper(ImmutableMap.of(AUTHORIZER,
authorizer));
- queryConfig = EasyMock.createMock(DefaultQueryConfig.class);
- queryConfigSupplier = () -> queryConfig;
+ queryConfig = EasyMock.createMock(QueryConfigProvider.class);
+ queryConfigProvider = queryConfig;
toolChest = EasyMock.createMock(QueryToolChest.class);
runner = EasyMock.createMock(QueryRunner.class);
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 9705e3cbb58..bf16044943e 100644
--- a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
@@ -23,7 +23,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
-import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -274,7 +273,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -324,7 +323,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(overrideConfig),
+ overrideConfig,
null
),
jsonMapper,
@@ -405,7 +404,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(overrideConfig),
+ overrideConfig,
null
),
jsonMapper,
@@ -506,7 +505,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -592,7 +591,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -691,7 +690,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -761,7 +760,7 @@ public class QueryResourceTest
queryResource = new QueryResource(
- new QueryLifecycleFactory(null, null, null, null, null, null,
NoopPolicyEnforcer.instance(), null, Suppliers.ofInstance(overrideConfig), null)
+ new QueryLifecycleFactory(null, null, null, null, null, null,
NoopPolicyEnforcer.instance(), null, overrideConfig, null)
{
@Override
public QueryLifecycle factorize()
@@ -838,7 +837,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(overrideConfig),
+ overrideConfig,
null
),
jsonMapper,
@@ -1110,7 +1109,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
authMapper,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -1192,7 +1191,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -1297,7 +1296,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
authMapper,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -1411,7 +1410,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
authMapper,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
@@ -1801,7 +1800,7 @@ public class QueryResourceTest
new AuthConfig(),
NoopPolicyEnforcer.instance(),
AuthTestUtils.TEST_AUTHORIZER_MAPPER,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null
),
jsonMapper,
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 ad57a1ca4e1..06e4df77c0a 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,6 +24,7 @@ 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.query.QueryContext;
import org.apache.druid.segment.TestHelper;
import org.apache.druid.server.QueryBlocklistRule;
import org.junit.Assert;
@@ -115,6 +116,32 @@ public class BrokerDynamicConfigTest
Assert.assertEquals(ImmutableMap.of("priority", "0"),
rule2.getContextMatches());
}
+ @Test
+ public void testSerdeWithQueryContext() throws Exception
+ {
+ String jsonStr = "{\n"
+ + " \"queryContext\": {\n"
+ + " \"priority\": 10,\n"
+ + " \"useCache\": false\n"
+ + " }\n"
+ + "}\n";
+
+ BrokerDynamicConfig actual = mapper.readValue(
+ mapper.writeValueAsString(mapper.readValue(jsonStr,
BrokerDynamicConfig.class)),
+ BrokerDynamicConfig.class
+ );
+
+ Assert.assertEquals(QueryContext.of(ImmutableMap.of("priority", 10,
"useCache", false)), actual.getQueryContext());
+ }
+
+ @Test
+ public void testNullQueryContextDefaultsToEmptyMap() throws Exception
+ {
+ BrokerDynamicConfig actual = mapper.readValue("{}",
BrokerDynamicConfig.class);
+ Assert.assertNotNull(actual.getQueryContext());
+ Assert.assertTrue(actual.getQueryContext().isEmpty());
+ }
+
@Test
public void testEquals()
{
diff --git a/services/src/main/java/org/apache/druid/cli/CliBroker.java
b/services/src/main/java/org/apache/druid/cli/CliBroker.java
index b9e39479b06..c622abc4fcc 100644
--- a/services/src/main/java/org/apache/druid/cli/CliBroker.java
+++ b/services/src/main/java/org/apache/druid/cli/CliBroker.java
@@ -26,6 +26,7 @@ import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.name.Names;
+import com.google.inject.util.Modules;
import org.apache.druid.client.BrokerSegmentWatcherConfig;
import org.apache.druid.client.BrokerServerView;
import org.apache.druid.client.BrokerViewOfBrokerConfig;
@@ -66,6 +67,7 @@ import org.apache.druid.msq.guice.MSQExternalDataSourceModule;
import org.apache.druid.msq.guice.MSQIndexingModule;
import org.apache.druid.msq.guice.MSQSqlModule;
import org.apache.druid.msq.guice.SqlTaskModule;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QuerySegmentWalker;
import org.apache.druid.query.RetryQueryRunnerConfig;
import org.apache.druid.query.lookup.LookupModule;
@@ -131,7 +133,9 @@ public class CliBroker extends ServerRunnable
return ImmutableList.of(
new BrokerProcessingModule(),
new QueryableModule(),
- new QueryRunnerFactoryModule(),
+ Modules.override(new QueryRunnerFactoryModule()).with(
+ overrideBinder ->
overrideBinder.bind(QueryConfigProvider.class).to(BrokerViewOfBrokerConfig.class)
+ ),
new SegmentWranglerModule(),
new JoinableFactoryModule(),
new BrokerServiceModule(),
diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java
b/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java
index 0410326687b..ff27f020832 100644
--- a/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java
+++ b/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java
@@ -43,7 +43,7 @@ import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.UOE;
import org.apache.druid.java.util.common.lifecycle.LifecycleStop;
import org.apache.druid.java.util.common.logger.Logger;
-import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContexts;
import org.apache.druid.server.security.Access;
import org.apache.druid.server.security.AuthenticationResult;
@@ -129,7 +129,7 @@ public class DruidMeta extends MetaImpl
);
private final SqlStatementFactory sqlStatementFactory;
- private final DefaultQueryConfig defaultQueryConfig;
+ private final QueryConfigProvider queryConfigProvider;
private final ScheduledExecutorService exec;
private final AvaticaServerConfig config;
private final List<Authenticator> authenticators;
@@ -150,7 +150,7 @@ public class DruidMeta extends MetaImpl
@Inject
public DruidMeta(
final @NativeQuery SqlStatementFactory sqlStatementFactory,
- final DefaultQueryConfig defaultQueryConfig,
+ final QueryConfigProvider queryConfigProvider,
final AvaticaServerConfig config,
final ErrorHandler errorHandler,
final AuthenticatorMapper authMapper
@@ -158,7 +158,7 @@ public class DruidMeta extends MetaImpl
{
this(
sqlStatementFactory,
- defaultQueryConfig,
+ queryConfigProvider,
config,
errorHandler,
Executors.newSingleThreadScheduledExecutor(
@@ -174,7 +174,7 @@ public class DruidMeta extends MetaImpl
public DruidMeta(
final SqlStatementFactory sqlStatementFactory,
- final DefaultQueryConfig defaultQueryConfig,
+ final QueryConfigProvider queryConfigProvider,
final AvaticaServerConfig config,
final ErrorHandler errorHandler,
final ScheduledExecutorService exec,
@@ -184,7 +184,7 @@ public class DruidMeta extends MetaImpl
{
super(null);
this.sqlStatementFactory = sqlStatementFactory;
- this.defaultQueryConfig = defaultQueryConfig;
+ this.queryConfigProvider = queryConfigProvider;
this.config = config;
this.errorHandler = errorHandler;
this.exec = exec;
@@ -267,7 +267,7 @@ public class DruidMeta extends MetaImpl
{
try {
final DruidJdbcStatement druidStatement = getDruidConnection(ch.id)
- .createStatement(sqlStatementFactory,
defaultQueryConfig.getContext(), fetcherFactory);
+ .createStatement(sqlStatementFactory,
queryConfigProvider.getContext(), fetcherFactory);
return new StatementHandle(ch.id, druidStatement.getStatementId(), null);
}
catch (Throwable t) {
@@ -291,14 +291,14 @@ public class DruidMeta extends MetaImpl
final DruidConnection druidConnection = getDruidConnection(ch.id);
final SqlQueryPlus sqlReq = SqlQueryPlus.builder()
.sql(sql)
-
.systemDefaultContext(defaultQueryConfig.getContext())
+
.systemDefaultContext(queryConfigProvider.getContext())
.queryContext(druidConnection.sessionContext())
.auth(doAuthenticate(druidConnection))
.buildJdbc();
final DruidJdbcPreparedStatement stmt =
getDruidConnection(ch.id).createPreparedStatement(
sqlStatementFactory,
sqlReq,
- defaultQueryConfig.getContext(),
+ queryConfigProvider.getContext(),
maxRowCount,
fetcherFactory
);
diff --git a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
index d5b6876129b..bac387f3d18 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
@@ -28,7 +28,7 @@ import
org.apache.druid.common.exception.ErrorResponseTransformStrategy;
import org.apache.druid.error.DruidException;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.logger.Logger;
-import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryContext;
import org.apache.druid.query.QueryContexts;
import org.apache.druid.server.QueryResource;
@@ -86,7 +86,7 @@ public class SqlResource
private final SqlResourceQueryResultPusherFactory resultPusherFactory;
private final SqlLifecycleManager sqlLifecycleManager;
private final SqlEngineRegistry sqlEngineRegistry;
- private final DefaultQueryConfig defaultQueryConfig;
+ private final QueryConfigProvider queryConfigProvider;
private final ServerConfig serverConfig;
@VisibleForTesting
@@ -96,7 +96,7 @@ public class SqlResource
final SqlLifecycleManager sqlLifecycleManager,
final SqlEngineRegistry sqlEngineRegistry,
final SqlResourceQueryResultPusherFactory resultPusherFactory,
- final DefaultQueryConfig defaultQueryConfig,
+ final QueryConfigProvider queryConfigProvider,
final ServerConfig serverConfig
)
{
@@ -104,7 +104,7 @@ public class SqlResource
this.sqlEngineRegistry = Preconditions.checkNotNull(sqlEngineRegistry,
"sqlEngineRegistry");
this.authorizerMapper = Preconditions.checkNotNull(authorizerMapper,
"authorizerMapper");
this.sqlLifecycleManager = Preconditions.checkNotNull(sqlLifecycleManager,
"sqlLifecycleManager");
- this.defaultQueryConfig = Preconditions.checkNotNull(defaultQueryConfig,
"defaultQueryConfig");
+ this.queryConfigProvider = Preconditions.checkNotNull(queryConfigProvider,
"queryConfigProvider");
this.serverConfig = serverConfig;
}
@@ -241,7 +241,7 @@ public class SqlResource
final QueryContext queryContext;
try {
- SqlQueryPlus sqlQueryPlus = makeSqlQueryPlus(sqlQuery, req,
defaultQueryConfig.getContext());
+ SqlQueryPlus sqlQueryPlus = makeSqlQueryPlus(sqlQuery, req,
queryConfigProvider.getContext());
// Redefine queryContext to include SET parameters and default context.
queryContext = new QueryContext(sqlQueryPlus.context());
diff --git
a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
index 00d2e771e78..2c963468ef8 100644
---
a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
+++
b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java
@@ -56,6 +56,7 @@ import
org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.query.BaseQuery;
import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryRunnerFactoryConglomerate;
import org.apache.druid.query.http.ClientSqlParameter;
import org.apache.druid.query.policy.NoopPolicyEnforcer;
@@ -286,6 +287,7 @@ public class DruidAvaticaHandlerTest extends CalciteTestBase
.toInstance(AuthConfig.newBuilder().setAuthorizeQueryContextParams(true).build());
binder.bind(DefaultQueryConfig.class)
.toInstance(new
DefaultQueryConfig(ImmutableMap.of("forbidden-key", "system-default-value")));
+
binder.bind(QueryConfigProvider.class).to(DefaultQueryConfig.class);
binder.bind(RequestLogger.class).toInstance(testRequestLogger);
binder.bind(DruidSchemaCatalog.class).toInstance(rootSchema);
for (NamedSchema schema : rootSchema.getNamedSchemas().values())
{
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryFrameworkUtils.java
b/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryFrameworkUtils.java
index e3f3a60c6a8..2ba1238cd2b 100644
---
a/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryFrameworkUtils.java
+++
b/sql/src/test/java/org/apache/druid/sql/calcite/util/QueryFrameworkUtils.java
@@ -19,7 +19,6 @@
package org.apache.druid.sql.calcite.util;
-import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Injector;
@@ -103,7 +102,7 @@ public class QueryFrameworkUtils
new AuthConfig(),
NoopPolicyEnforcer.instance(),
authorizerMapper,
- Suppliers.ofInstance(new DefaultQueryConfig(Map.of())),
+ new DefaultQueryConfig(Map.of()),
null // BrokerConfigManager - null for tests
);
}
diff --git a/sql/src/test/java/org/apache/druid/sql/guice/SqlModuleTest.java
b/sql/src/test/java/org/apache/druid/sql/guice/SqlModuleTest.java
index 91440ee83a6..8c86d913d67 100644
--- a/sql/src/test/java/org/apache/druid/sql/guice/SqlModuleTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/guice/SqlModuleTest.java
@@ -19,14 +19,11 @@
package org.apache.druid.sql.guice;
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
-import com.google.inject.TypeLiteral;
import org.apache.druid.client.BrokerViewOfCoordinatorConfig;
import org.apache.druid.client.FilteredServerInventoryView;
import org.apache.druid.client.TimelineServerView;
@@ -49,6 +46,7 @@ import org.apache.druid.java.util.http.client.HttpClient;
import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.GenericQueryMetricsFactory;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.query.QueryRunnerFactoryConglomerate;
import org.apache.druid.query.QuerySegmentWalker;
import org.apache.druid.query.QueryToolChestWarehouse;
@@ -200,7 +198,7 @@ public class SqlModuleTest
binder.bind(Escalator.class).toInstance(new NoopEscalator());
binder.bind(ServiceEmitter.class).toInstance(serviceEmitter);
binder.bind(RequestLogger.class).toInstance(NoopRequestLogger.instance());
- binder.bind(new
TypeLiteral<Supplier<DefaultQueryConfig>>(){}).toInstance(Suppliers.ofInstance(new
DefaultQueryConfig(null)));
+
binder.bind(QueryConfigProvider.class).to(DefaultQueryConfig.class);
binder.bind(FilteredServerInventoryView.class).toInstance(inventoryView);
binder.bind(TimelineServerView.class).toInstance(timelineServerView);
binder.bind(DruidNodeDiscoveryProvider.class).toInstance(druidNodeDiscoveryProvider);
diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlHttpModuleTest.java
b/sql/src/test/java/org/apache/druid/sql/http/SqlHttpModuleTest.java
index efed7674620..87a501f00a8 100644
--- a/sql/src/test/java/org/apache/druid/sql/http/SqlHttpModuleTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/http/SqlHttpModuleTest.java
@@ -32,6 +32,7 @@ import org.apache.druid.guice.annotations.Json;
import org.apache.druid.guice.annotations.NativeQuery;
import org.apache.druid.guice.annotations.Self;
import org.apache.druid.query.DefaultQueryConfig;
+import org.apache.druid.query.QueryConfigProvider;
import org.apache.druid.server.DruidNode;
import org.apache.druid.server.ResponseContextConfig;
import org.apache.druid.server.security.AuthorizerMapper;
@@ -74,6 +75,7 @@ public class SqlHttpModuleTest
.annotatedWith(NativeQuery.class)
.toInstance(EasyMock.mock(SqlStatementFactory.class));
binder.bind(DefaultQueryConfig.class).toInstance(DefaultQueryConfig.NIL);
+ binder.bind(QueryConfigProvider.class).to(DefaultQueryConfig.class);
},
target
);
diff --git
a/web-console/src/dialogs/broker-dynamic-config-dialog/broker-dynamic-config-completions.ts
b/web-console/src/dialogs/broker-dynamic-config-dialog/broker-dynamic-config-completions.ts
index 7572d838e9e..6620dc721f0 100644
---
a/web-console/src/dialogs/broker-dynamic-config-dialog/broker-dynamic-config-completions.ts
+++
b/web-console/src/dialogs/broker-dynamic-config-dialog/broker-dynamic-config-completions.ts
@@ -29,6 +29,32 @@ export const BROKER_DYNAMIC_CONFIG_COMPLETIONS:
JsonCompletionRule[] = [
documentation:
'List of rules to block queries on brokers. Each rule can match by
datasource, query type, and/or context parameters.',
},
+ {
+ value: 'queryContext',
+ documentation:
+ 'Default query context values applied to all queries on this broker.
These override static defaults from runtime properties but are overridden by
per-query context values. Useful for setting cluster-wide defaults like
priority or timeout without restarting.',
+ },
+ ],
+ },
+ // Query context key suggestions
+ {
+ path: '$.queryContext',
+ isObject: true,
+ completions: [
+ { value: 'priority', documentation: 'Query scheduling priority' },
+ { value: 'timeout', documentation: 'Query timeout in milliseconds' },
+ { value: 'useCache', documentation: 'Whether to use the query cache' },
+ { value: 'populateCache', documentation: 'Whether to populate the query
cache' },
+ { value: 'useResultLevelCache', documentation: 'Whether to use
result-level caching' },
+ {
+ value: 'populateResultLevelCache',
+ documentation: 'Whether to populate the result-level cache',
+ },
+ { value: 'maxScatterGatherBytes', documentation: 'Max bytes to gather
across segments' },
+ {
+ value: 'maxQueuedBytes',
+ documentation: 'Max bytes queued before backpressure kicks in',
+ },
],
},
// Query blocklist rule properties
diff --git
a/web-console/src/druid-models/broker-dynamic-config/broker-dynamic-config.tsx
b/web-console/src/druid-models/broker-dynamic-config/broker-dynamic-config.tsx
index 63ce036d3cb..9fc66cd4fa1 100644
---
a/web-console/src/druid-models/broker-dynamic-config/broker-dynamic-config.tsx
+++
b/web-console/src/druid-models/broker-dynamic-config/broker-dynamic-config.tsx
@@ -20,6 +20,7 @@ import type { Field } from '../../components';
export interface BrokerDynamicConfig {
queryBlocklist?: QueryBlocklistRule[];
+ queryContext?: Record<string, unknown>;
}
export interface QueryBlocklistRule {
@@ -29,4 +30,25 @@ export interface QueryBlocklistRule {
contextMatches?: Record<string, string>;
}
-export const BROKER_DYNAMIC_CONFIG_FIELDS: Field<BrokerDynamicConfig>[] = [];
+export const BROKER_DYNAMIC_CONFIG_FIELDS: Field<BrokerDynamicConfig>[] = [
+ {
+ name: 'queryContext',
+ type: 'json',
+ info: (
+ <>
+ Default query context values applied to all queries on this broker.
These override static
+ defaults from runtime properties but are overridden by per-query
context values.
+ </>
+ ),
+ },
+ {
+ name: 'queryBlocklist',
+ type: 'json',
+ info: (
+ <>
+ List of rules to block queries on brokers. Each rule can match by
datasource, query type,
+ and/or context parameters.
+ </>
+ ),
+ },
+];
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]