This is an automated email from the ASF dual-hosted git repository.
abhishekrb19 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 2a0edc5a8de refactor: Make QueryBlocklistRule an interface for
extensibility (#19457)
2a0edc5a8de is described below
commit 2a0edc5a8de887155ceff000f58c1fbd2c6ed724
Author: Abhishek Radhakrishnan <[email protected]>
AuthorDate: Mon May 18 10:23:02 2026 -0700
refactor: Make QueryBlocklistRule an interface for extensibility (#19457)
- Convert QueryBlocklistRule from a concrete class to an interface with two
methods: getRuleName() and matches(Query<?>).
- Moves the existing implementation to DefaultQueryBlocklistRule with a
default impl on the interface @JsonTypeInfo(defaultImpl =
DefaultQueryBlocklistRule.class) so existing JSON configs without a "type"
field continue to deserialize correctly.
- Extensions can register new rule types as Jackson subtypes via
SimpleModule.registerSubtypes(...) and we can even introduce new union rule
types with different logic w/o polluting existing default implementation.
---
docs/api-reference/dynamic-configuration-api.md | 35 ++++-
.../server/EmbeddedBrokerDynamicConfigTest.java | 3 +-
...istRule.java => DefaultQueryBlocklistRule.java} | 26 ++--
.../apache/druid/server/QueryBlocklistRule.java | 171 +++------------------
.../coordinator/CoordinatorClientImplTest.java | 4 +-
.../druid/server/QueryBlocklistRuleTest.java | 52 +++++--
.../apache/druid/server/QueryLifecycleTest.java | 4 +-
.../server/broker/BrokerDynamicConfigTest.java | 30 +++-
8 files changed, 142 insertions(+), 183 deletions(-)
diff --git a/docs/api-reference/dynamic-configuration-api.md
b/docs/api-reference/dynamic-configuration-api.md
index 259e50c8800..7805d7287c4 100644
--- a/docs/api-reference/dynamic-configuration-api.md
+++ b/docs/api-reference/dynamic-configuration-api.md
@@ -371,6 +371,7 @@ Host: http://ROUTER_IP:ROUTER_PORT
{
"queryBlocklist": [
{
+ "type": "default",
"ruleName": "block-expensive-scans",
"dataSources": ["large_table"],
"queryTypes": ["scan"]
@@ -440,11 +441,13 @@ curl -X POST
"http://ROUTER_IP:ROUTER_PORT/druid/coordinator/v1/broker/config" \
-d '{
"queryBlocklist": [
{
+ "type": "default",
"ruleName": "block-expensive-scans",
"dataSources": ["large_table", "huge_dataset"],
"queryTypes": ["scan"]
},
{
+ "type": "default",
"ruleName": "block-debug-queries",
"contextMatches": {
"debug": "true"
@@ -481,11 +484,13 @@ X-Druid-Comment: Add query blocklist rules and set
default context
{
"queryBlocklist": [
{
+ "type": "default",
"ruleName": "block-expensive-scans",
"dataSources": ["large_table", "huge_dataset"],
"queryTypes": ["scan"]
},
{
+ "type": "default",
"ruleName": "block-debug-queries",
"contextMatches": {
"debug": "true"
@@ -529,22 +534,42 @@ The following table shows the dynamic configuration
properties for the Broker.
Query blocklist rules allow you to block specific queries based on datasource,
query type, and/or query context parameters. This feature is useful for
preventing expensive or problematic queries from impacting cluster performance.
-Each rule in the `queryBlocklist` array is a JSON object with the following
properties:
+Each rule in the `queryBlocklist` array is a JSON object. The `type` field
selects the rule implementation. If omitted, it defaults to `"default"`. A
query is blocked if it matches ANY rule in the blocklist (OR logic between
rules).
+
+> **Note:** The `"type"` field is not required for `"default"` rules (existing
configs without it continue to work), but including it explicitly is
recommended for clarity.
+
+##### `default` type
+
+The built-in rule type. Blocks queries by matching on datasource, query type,
and/or query context parameters.
|Property|Description|Required|Default|
|--------|-----------|--------|-------|
+|`type`|Rule type identifier. Not required — rules without a `"type"` field
are treated as `"default"` for backwards compatibility with existing
configurations.|No|`"default"`|
|`ruleName`|Unique name identifying this blocklist rule. Used in error
messages when queries are blocked.|Yes|N/A|
|`dataSources`|List of datasource names to match. A query matches if it
references any datasource in this list.|No|Matches all datasources|
|`queryTypes`|List of query types to match (e.g., `scan`, `timeseries`,
`groupBy`, `topN`). A query matches if its type is in this list.|No|Matches all
query types|
|`contextMatches`|Map of query context parameter key-value pairs to match. A
query matches if all specified context parameters match the provided values
(case-sensitive string comparison).|No|Matches all contexts|
-**Rule matching behavior:**
+**Matching behavior:**
- A query must match ALL specified criteria within a rule (AND logic) to be
blocked by that rule
-- If any criterion is omitted, empty or null, it matches everything (e.g.,
omitting `queryTypes` or setting it to null matches all query types)
+- If any criterion is omitted, empty, or null, it matches everything (e.g.,
omitting `queryTypes` or setting it to null matches all query types)
- For context matching: if a rule specifies context parameters, queries with
missing or null values for those keys will not match
- 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)
+
+##### Custom types (extensions)
+
+Extensions can register additional rule types by adding Jackson subtypes to
the `QueryBlocklistRule` interface. A custom rule is selected by setting
`"type"` to its registered name:
+
+```json
+{
+ "type": "myCustomRule",
+ "ruleName": "rate-limit-heavy-users",
+ ...
+}
+```
+
+Custom types define their own matching semantics — they may use different
criteria and logic than the `default` type described above. If a rule
references a type whose extension is not loaded, deserialization fails with an
error rather than silently falling back to the default type.
**Error response:**
@@ -676,7 +701,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\"]}],\"queryContext\":{\"priority\":0,\"timeout\":300000},\"perSegmentTimeoutConfig\":{\"large_table\":{\"perSegmentTimeoutMs\":10000,\"monitorOnly\":false}}}",
+ "payload":
"{\"queryBlocklist\":[{\"type\":\"default\",\"ruleName\":\"block-expensive-scans\",\"dataSources\":[\"large_table\"],\"queryTypes\":[\"scan\"]}],\"queryContext\":{\"priority\":0,\"timeout\":300000},\"perSegmentTimeoutConfig\":{\"large_table\":{\"perSegmentTimeoutMs\":10000,\"monitorOnly\":false}}}",
"auditTime": "2024-03-06T12:00:00.000Z"
}
]
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
index 34020229434..ac48e2c5346 100644
---
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
@@ -24,6 +24,7 @@ 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.DefaultQueryBlocklistRule;
import org.apache.druid.server.QueryBlocklistRule;
import org.apache.druid.server.broker.BrokerDynamicConfig;
import org.apache.druid.server.http.BrokerDynamicConfigSyncer;
@@ -102,7 +103,7 @@ public class EmbeddedBrokerDynamicConfigTest extends
EmbeddedClusterTestBase
Assertions.assertFalse(initialResult.isBlank());
// Apply blocklist rule that matches all queries on this datasource
- QueryBlocklistRule blockRule = new QueryBlocklistRule(
+ QueryBlocklistRule blockRule = new DefaultQueryBlocklistRule(
"block-test-datasource",
Set.of(dataSource),
null,
diff --git
a/server/src/main/java/org/apache/druid/server/QueryBlocklistRule.java
b/server/src/main/java/org/apache/druid/server/DefaultQueryBlocklistRule.java
similarity index 85%
copy from server/src/main/java/org/apache/druid/server/QueryBlocklistRule.java
copy to
server/src/main/java/org/apache/druid/server/DefaultQueryBlocklistRule.java
index 1242a11bff3..ae1d8662d28 100644
--- a/server/src/main/java/org/apache/druid/server/QueryBlocklistRule.java
+++
b/server/src/main/java/org/apache/druid/server/DefaultQueryBlocklistRule.java
@@ -32,10 +32,13 @@ import java.util.Objects;
import java.util.Set;
/**
- * A rule for matching queries against blocklist criteria. A query matches
this rule if ALL
- * specified criteria match (AND logic). Null or empty criteria match
everything.
+ * Default {@link QueryBlocklistRule} implementation. A query matches if ALL
specified criteria
+ * match (AND logic). Null or empty criteria are wildcards (match everything).
+ *
+ * <p>At least one criterion must be non-empty to prevent accidentally
blocking all queries.
+ * Deserializes from JSON with no {@code "type"} field for backwards
compatibility.
*/
-public class QueryBlocklistRule
+public class DefaultQueryBlocklistRule implements QueryBlocklistRule
{
private final String ruleName;
@Nullable
@@ -50,7 +53,7 @@ public class QueryBlocklistRule
private final boolean hasContextCriteria;
@JsonCreator
- public QueryBlocklistRule(
+ public DefaultQueryBlocklistRule(
@JsonProperty("ruleName") String ruleName,
@JsonProperty("dataSources") @Nullable Set<String> dataSources,
@JsonProperty("queryTypes") @Nullable Set<String> queryTypes,
@@ -62,7 +65,6 @@ public class QueryBlocklistRule
"ruleName must not be null or empty"
);
- // At least one criterion must be specified to prevent accidentally
blocking all queries
this.hasDataSourceCriteria = dataSources != null && !dataSources.isEmpty();
this.hasQueryTypeCriteria = queryTypes != null && !queryTypes.isEmpty();
this.hasContextCriteria = contextMatches != null &&
!contextMatches.isEmpty();
@@ -79,6 +81,7 @@ public class QueryBlocklistRule
this.contextMatches = contextMatches;
}
+ @Override
@JsonProperty
public String getRuleName()
{
@@ -106,13 +109,7 @@ public class QueryBlocklistRule
return contextMatches;
}
- /**
- * Returns true if the query matches ALL specified criteria (AND logic).
- * Null or empty criteria match everything.
- *
- * @param query the query to check
- * @return true if the query matches this rule, false otherwise
- */
+ @Override
public boolean matches(Query<?> query)
{
if (hasDataSourceCriteria) {
@@ -131,7 +128,6 @@ public class QueryBlocklistRule
if (hasContextCriteria) {
for (Map.Entry<String, String> entry : contextMatches.entrySet()) {
Object contextValue = query.getContext().get(entry.getKey());
- // If the query context doesn't have this key or has a null value, it
doesn't match
if (contextValue == null ||
!entry.getValue().equals(String.valueOf(contextValue))) {
return false;
}
@@ -150,7 +146,7 @@ public class QueryBlocklistRule
if (o == null || getClass() != o.getClass()) {
return false;
}
- QueryBlocklistRule that = (QueryBlocklistRule) o;
+ DefaultQueryBlocklistRule that = (DefaultQueryBlocklistRule) o;
return Objects.equals(ruleName, that.ruleName)
&& Objects.equals(dataSources, that.dataSources)
&& Objects.equals(queryTypes, that.queryTypes)
@@ -166,7 +162,7 @@ public class QueryBlocklistRule
@Override
public String toString()
{
- return "QueryBlocklistRule{" +
+ return "DefaultQueryBlocklistRule{" +
"ruleName='" + ruleName + '\'' +
", dataSources=" + dataSources +
", queryTypes=" + queryTypes +
diff --git
a/server/src/main/java/org/apache/druid/server/QueryBlocklistRule.java
b/server/src/main/java/org/apache/druid/server/QueryBlocklistRule.java
index 1242a11bff3..55b20c262a3 100644
--- a/server/src/main/java/org/apache/druid/server/QueryBlocklistRule.java
+++ b/server/src/main/java/org/apache/druid/server/QueryBlocklistRule.java
@@ -19,158 +19,37 @@
package org.apache.druid.server;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.Sets;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
+import org.apache.druid.jackson.StrictTypeIdResolver;
import org.apache.druid.query.Query;
-import javax.annotation.Nullable;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
/**
- * A rule for matching queries against blocklist criteria. A query matches
this rule if ALL
- * specified criteria match (AND logic). Null or empty criteria match
everything.
+ * A rule that determines whether a query should be blocked. Implementations
define their own
+ * matching logic and JSON fields. Use {@link DefaultQueryBlocklistRule} for
the standard criteria
+ * (datasources, query types, context).
+ *
+ * <p>Rules with no {@code "type"} field in JSON deserialize as {@link
DefaultQueryBlocklistRule}
+ * for backwards compatibility. An explicit but unrecognized {@code "type"}
value will fail
+ * deserialization rather than silently falling back to the default — this
prevents extension
+ * rules from being misinterpreted when the extension is not loaded.
Extensions can register
+ * additional implementations as Jackson subtypes via {@code
SimpleModule.registerSubtypes(...)}.
+ *
+ * <p>Implementations must define {@code equals} and {@code hashCode} so that
+ * {@link org.apache.druid.server.broker.BrokerDynamicConfig} can detect
changes correctly.
*/
-public class QueryBlocklistRule
+@JsonTypeResolver(StrictTypeIdResolver.Builder.class)
+@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type", defaultImpl =
DefaultQueryBlocklistRule.class)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = DefaultQueryBlocklistRule.class, name =
"default")
+})
+public interface QueryBlocklistRule
{
- private final String ruleName;
- @Nullable
- private final Set<String> dataSources;
- @Nullable
- private final Set<String> queryTypes;
- @Nullable
- private final Map<String, String> contextMatches;
-
- private final boolean hasDataSourceCriteria;
- private final boolean hasQueryTypeCriteria;
- private final boolean hasContextCriteria;
-
- @JsonCreator
- public QueryBlocklistRule(
- @JsonProperty("ruleName") String ruleName,
- @JsonProperty("dataSources") @Nullable Set<String> dataSources,
- @JsonProperty("queryTypes") @Nullable Set<String> queryTypes,
- @JsonProperty("contextMatches") @Nullable Map<String, String>
contextMatches
- )
- {
- Preconditions.checkArgument(
- !Strings.isNullOrEmpty(ruleName),
- "ruleName must not be null or empty"
- );
-
- // At least one criterion must be specified to prevent accidentally
blocking all queries
- this.hasDataSourceCriteria = dataSources != null && !dataSources.isEmpty();
- this.hasQueryTypeCriteria = queryTypes != null && !queryTypes.isEmpty();
- this.hasContextCriteria = contextMatches != null &&
!contextMatches.isEmpty();
-
- Preconditions.checkArgument(
- hasDataSourceCriteria || hasQueryTypeCriteria || hasContextCriteria,
- "At least one criterion (dataSources, queryTypes, or contextMatches)
must be specified. "
- + "A rule with all null/empty criteria would block ALL queries."
- );
-
- this.ruleName = ruleName;
- this.dataSources = dataSources;
- this.queryTypes = queryTypes;
- this.contextMatches = contextMatches;
- }
-
- @JsonProperty
- public String getRuleName()
- {
- return ruleName;
- }
-
- @JsonProperty
- @Nullable
- public Set<String> getDataSources()
- {
- return dataSources;
- }
-
- @JsonProperty
- @Nullable
- public Set<String> getQueryTypes()
- {
- return queryTypes;
- }
-
- @JsonProperty
- @Nullable
- public Map<String, String> getContextMatches()
- {
- return contextMatches;
- }
+ String getRuleName();
/**
- * Returns true if the query matches ALL specified criteria (AND logic).
- * Null or empty criteria match everything.
- *
- * @param query the query to check
- * @return true if the query matches this rule, false otherwise
+ * Returns true if the query matches this rule and should be blocked.
*/
- public boolean matches(Query<?> query)
- {
- if (hasDataSourceCriteria) {
- Set<String> queryDatasources = query.getDataSource().getTableNames();
- if (Sets.intersection(dataSources, queryDatasources).isEmpty()) {
- return false;
- }
- }
-
- if (hasQueryTypeCriteria) {
- if (!queryTypes.contains(query.getType())) {
- return false;
- }
- }
-
- if (hasContextCriteria) {
- for (Map.Entry<String, String> entry : contextMatches.entrySet()) {
- Object contextValue = query.getContext().get(entry.getKey());
- // If the query context doesn't have this key or has a null value, it
doesn't match
- if (contextValue == null ||
!entry.getValue().equals(String.valueOf(contextValue))) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- QueryBlocklistRule that = (QueryBlocklistRule) o;
- return Objects.equals(ruleName, that.ruleName)
- && Objects.equals(dataSources, that.dataSources)
- && Objects.equals(queryTypes, that.queryTypes)
- && Objects.equals(contextMatches, that.contextMatches);
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hash(ruleName, dataSources, queryTypes, contextMatches);
- }
-
- @Override
- public String toString()
- {
- return "QueryBlocklistRule{" +
- "ruleName='" + ruleName + '\'' +
- ", dataSources=" + dataSources +
- ", queryTypes=" + queryTypes +
- ", contextMatches=" + contextMatches +
- '}';
- }
+ boolean matches(Query<?> query);
}
diff --git
a/server/src/test/java/org/apache/druid/client/coordinator/CoordinatorClientImplTest.java
b/server/src/test/java/org/apache/druid/client/coordinator/CoordinatorClientImplTest.java
index d915ece0c2e..0b39eb15016 100644
---
a/server/src/test/java/org/apache/druid/client/coordinator/CoordinatorClientImplTest.java
+++
b/server/src/test/java/org/apache/druid/client/coordinator/CoordinatorClientImplTest.java
@@ -49,7 +49,7 @@ import org.apache.druid.rpc.RequestBuilder;
import org.apache.druid.segment.column.ColumnType;
import org.apache.druid.segment.column.RowSignature;
import org.apache.druid.segment.metadata.DataSourceInformation;
-import org.apache.druid.server.QueryBlocklistRule;
+import org.apache.druid.server.DefaultQueryBlocklistRule;
import org.apache.druid.server.broker.BrokerDynamicConfig;
import org.apache.druid.server.compaction.CompactionStatusResponse;
import org.apache.druid.server.coordination.DruidServerMetadata;
@@ -840,7 +840,7 @@ public class CoordinatorClientImplTest
{
final BrokerDynamicConfig brokerDynamicConfig =
BrokerDynamicConfig.builder().withQueryBlocklist(
List.of(
- new QueryBlocklistRule("test", Set.of("dataSource"), null, null)
+ new DefaultQueryBlocklistRule("test", Set.of("dataSource"), null,
null)
)
).build();
diff --git
a/server/src/test/java/org/apache/druid/server/QueryBlocklistRuleTest.java
b/server/src/test/java/org/apache/druid/server/QueryBlocklistRuleTest.java
index 1594143b938..21c2b3852a4 100644
--- a/server/src/test/java/org/apache/druid/server/QueryBlocklistRuleTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryBlocklistRuleTest.java
@@ -19,10 +19,12 @@
package org.apache.druid.server;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.druid.query.Druids;
import org.apache.druid.query.timeseries.TimeseriesQuery;
+import org.apache.druid.segment.TestHelper;
import org.junit.Assert;
import org.junit.Test;
import java.util.Map;
@@ -36,7 +38,7 @@ public class QueryBlocklistRuleTest
// Rule with all null criteria would block ALL queries - this should be
rejected
Assert.assertThrows(
IllegalArgumentException.class,
- () -> new QueryBlocklistRule("match-all", null, null, null)
+ () -> new DefaultQueryBlocklistRule("match-all", null, null, null)
);
}
@@ -46,7 +48,7 @@ public class QueryBlocklistRuleTest
// Rule with all empty collections should also be rejected (same as null)
Assert.assertThrows(
IllegalArgumentException.class,
- () -> new QueryBlocklistRule("match-all", ImmutableSet.of(),
ImmutableSet.of(), ImmutableMap.of())
+ () -> new DefaultQueryBlocklistRule("match-all", ImmutableSet.of(),
ImmutableSet.of(), ImmutableMap.of())
);
}
@@ -54,7 +56,7 @@ public class QueryBlocklistRuleTest
public void testMatchByDataSource()
{
Set<String> dataSources = ImmutableSet.of("sensitive_data", "pii_table");
- QueryBlocklistRule rule = new QueryBlocklistRule("block-sensitive",
dataSources, null, null);
+ QueryBlocklistRule rule = new DefaultQueryBlocklistRule("block-sensitive",
dataSources, null, null);
// Should match when datasource is in the list
TimeseriesQuery matchingQuery = Druids.newTimeseriesQueryBuilder()
@@ -75,7 +77,7 @@ public class QueryBlocklistRuleTest
public void testMatchByContext()
{
Map<String, String> contextMatches = ImmutableMap.of("priority", "0",
"application", "rogue-app");
- QueryBlocklistRule rule = new QueryBlocklistRule("block-rogue-app", null,
null, contextMatches);
+ QueryBlocklistRule rule = new DefaultQueryBlocklistRule("block-rogue-app",
null, null, contextMatches);
// Should match when all context values match
TimeseriesQuery matchingQuery = Druids.newTimeseriesQueryBuilder()
@@ -105,7 +107,7 @@ public class QueryBlocklistRuleTest
public void testMatchByQueryType()
{
Set<String> queryTypes = ImmutableSet.of("timeseries", "groupBy");
- QueryBlocklistRule rule = new
QueryBlocklistRule("block-timeseries-groupby", null, queryTypes, null);
+ QueryBlocklistRule rule = new
DefaultQueryBlocklistRule("block-timeseries-groupby", null, queryTypes, null);
// Should match when query type is in the list (timeseries)
TimeseriesQuery matchingQuery = Druids.newTimeseriesQueryBuilder()
@@ -121,7 +123,7 @@ public class QueryBlocklistRuleTest
// Rule with multiple criteria - all must match (AND logic)
Set<String> dataSources = ImmutableSet.of("large_table");
Map<String, String> contextMatches = ImmutableMap.of("priority", "0");
- QueryBlocklistRule rule = new QueryBlocklistRule(
+ QueryBlocklistRule rule = new DefaultQueryBlocklistRule(
"block-low-priority-large-table",
dataSources,
null,
@@ -156,7 +158,7 @@ public class QueryBlocklistRuleTest
@Test
public void testWildcardBehavior_nullQueryTypes()
{
- QueryBlocklistRule rule = new QueryBlocklistRule(
+ QueryBlocklistRule rule = new DefaultQueryBlocklistRule(
"block-datasource-all-types",
ImmutableSet.of("blocked_ds"),
null, // null means match all query types
@@ -177,7 +179,7 @@ public class QueryBlocklistRuleTest
// Rule name cannot be null
Assert.assertThrows(
IllegalArgumentException.class,
- () -> new QueryBlocklistRule(null, ImmutableSet.of("ds"), null, null)
+ () -> new DefaultQueryBlocklistRule(null, ImmutableSet.of("ds"), null,
null)
);
}
@@ -187,7 +189,39 @@ public class QueryBlocklistRuleTest
// Rule name cannot be empty
Assert.assertThrows(
IllegalArgumentException.class,
- () -> new QueryBlocklistRule("", ImmutableSet.of("ds"), null, null)
+ () -> new DefaultQueryBlocklistRule("", ImmutableSet.of("ds"), null,
null)
);
}
+
+ @Test
+ public void testDeserialize_missingType_usesDefault() throws Exception
+ {
+ ObjectMapper mapper = TestHelper.makeJsonMapper();
+ String json = "{\"ruleName\":\"block-ds\",\"dataSources\":[\"foo\"]}";
+ QueryBlocklistRule rule = mapper.readValue(json, QueryBlocklistRule.class);
+ Assert.assertTrue(rule instanceof DefaultQueryBlocklistRule);
+ Assert.assertEquals("block-ds", rule.getRuleName());
+ }
+
+ @Test
+ public void testDeserialize_explicitDefaultType() throws Exception
+ {
+ ObjectMapper mapper = TestHelper.makeJsonMapper();
+ String json =
"{\"type\":\"default\",\"ruleName\":\"block-ds\",\"dataSources\":[\"foo\"]}";
+ QueryBlocklistRule rule = mapper.readValue(json, QueryBlocklistRule.class);
+ Assert.assertTrue(rule instanceof DefaultQueryBlocklistRule);
+ Assert.assertEquals("block-ds", rule.getRuleName());
+ }
+
+ @Test
+ public void testDeserialize_unrecognizedType_fails()
+ {
+ ObjectMapper mapper = TestHelper.makeJsonMapper();
+ String json =
"{\"type\":\"customExtension\",\"ruleName\":\"block-ds\",\"dataSources\":[\"foo\"]}";
+ Exception e = Assert.assertThrows(
+ Exception.class,
+ () -> mapper.readValue(json, QueryBlocklistRule.class)
+ );
+ Assert.assertTrue(e.getMessage().contains("Could not resolve type id
'customExtension'"));
+ }
}
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 96880147f40..5f8da0d21a3 100644
--- a/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryLifecycleTest.java
@@ -830,7 +830,7 @@ public class QueryLifecycleTest
public void testRunSimple_queryBlocklisted()
{
// Create a blocklist rule that matches our test query
- QueryBlocklistRule rule = new QueryBlocklistRule(
+ QueryBlocklistRule rule = new DefaultQueryBlocklistRule(
"test-rule",
ImmutableSet.of(DATASOURCE),
null,
@@ -880,7 +880,7 @@ public class QueryLifecycleTest
public void testRunSimple_queryNotBlocklisted()
{
// Create a blocklist rule that does NOT match our test query
- QueryBlocklistRule rule = new QueryBlocklistRule(
+ QueryBlocklistRule rule = new DefaultQueryBlocklistRule(
"test-rule",
ImmutableSet.of("other_datasource"),
null,
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 425ccbdd14e..6a466bcd269 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
@@ -26,6 +26,7 @@ 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.DefaultQueryBlocklistRule;
import org.apache.druid.server.QueryBlocklistRule;
import org.junit.Assert;
import org.junit.Test;
@@ -60,12 +61,35 @@ public class BrokerDynamicConfigTest
);
List<QueryBlocklistRule> expectedBlocklist = ImmutableList.of(
- new QueryBlocklistRule("block-wikipedia",
ImmutableSet.of("wikipedia"), null, null)
+ new DefaultQueryBlocklistRule("block-wikipedia",
ImmutableSet.of("wikipedia"), null, null)
);
Assert.assertEquals(expectedBlocklist, actual.getQueryBlocklist());
}
+ @Test
+ public void testSerdeWithExplicitDefaultType() throws Exception
+ {
+ String jsonStr = "{\n"
+ + " \"queryBlocklist\": [\n"
+ + " {\n"
+ + " \"type\": \"default\",\n"
+ + " \"ruleName\": \"block-wikipedia\",\n"
+ + " \"dataSources\": [\"wikipedia\"]\n"
+ + " }\n"
+ + " ]\n"
+ + "}\n";
+
+ BrokerDynamicConfig actual = mapper.readValue(jsonStr,
BrokerDynamicConfig.class);
+
+ Assert.assertEquals(1, actual.getQueryBlocklist().size());
+ Assert.assertTrue(actual.getQueryBlocklist().get(0) instanceof
DefaultQueryBlocklistRule);
+ Assert.assertEquals(
+ new DefaultQueryBlocklistRule("block-wikipedia",
ImmutableSet.of("wikipedia"), null, null),
+ actual.getQueryBlocklist().get(0)
+ );
+ }
+
@Test
public void testSerdeWithNullBlocklist() throws Exception
{
@@ -108,11 +132,11 @@ public class BrokerDynamicConfigTest
Assert.assertNotNull(actual.getQueryBlocklist());
Assert.assertEquals(2, actual.getQueryBlocklist().size());
- QueryBlocklistRule rule1 = actual.getQueryBlocklist().get(0);
+ DefaultQueryBlocklistRule rule1 = (DefaultQueryBlocklistRule)
actual.getQueryBlocklist().get(0);
Assert.assertEquals("block-scan-queries", rule1.getRuleName());
Assert.assertEquals(ImmutableSet.of("scan"), rule1.getQueryTypes());
- QueryBlocklistRule rule2 = actual.getQueryBlocklist().get(1);
+ DefaultQueryBlocklistRule rule2 = (DefaultQueryBlocklistRule)
actual.getQueryBlocklist().get(1);
Assert.assertEquals("block-context", rule2.getRuleName());
Assert.assertEquals(ImmutableMap.of("priority", "0"),
rule2.getContextMatches());
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]