This is an automated email from the ASF dual-hosted git repository.
fanng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new eaff8f5749 [#10097] feat(core): support builtin iceberg compaction
policy (#10189)
eaff8f5749 is described below
commit eaff8f5749e64f4768eb469b90494f0356f1f2a7
Author: FANNG <[email protected]>
AuthorDate: Mon Mar 9 12:34:11 2026 +0900
[#10097] feat(core): support builtin iceberg compaction policy (#10189)
### What changes were proposed in this pull request?
This PR adds a built-in Iceberg compaction policy with typed content and
aligns policy type handling to enum-based built-in types.
1. Added typed policy content for ICEBERG_COMPACTION (instead of
CustomContent):
- minDatafileMse
- minDeleteFileNumber
- rewriteOptions (expanded to job.options.*)
2. Added built-in compaction defaults in policy content:
- strategy.type=compaction
- job.template-name=builtin-iceberg-rewrite-data-files
- built-in trigger/score expressions based on custom-datafile_mse and
custom-
delete_file_number
3. Added DTO/REST/convertor support for typed content serialization/
deserialization.
4. Added core-side built-in policy content type validation to prevent
mismatched content type.
5. Aligned built-in policy type with enum and prefix:
- removed obsolete TODO in Policy.BuiltInType
- ICEBERG_COMPACTION.policyType() now uses system_iceberg_compaction
- REST policy type output is aligned to enum name (ICEBERG_COMPACTION)
### Why are the changes needed?
- iceberg-compaction is a core built-in policy and should be strongly
typed,
validated, and stable, rather than relying on generic CustomContent.
- Strong typing improves correctness, validation, and compatibility
across
API/DTO/core/optimizer integration.
- Enum + built-in prefix alignment standardizes policy type semantics
and
avoids ambiguous type representations.
Fix: #10097
### Does this PR introduce any user-facing change?
Yes.
1. A built-in policy type for Iceberg compaction is available with typed
content fields:
- minDatafileMse
- minDeleteFileNumber
- rewriteOptions
2. Built-in policy type representation is aligned:
- built-in type value: system_iceberg_compaction
- REST response policy type for this built-in policy: ICEBERG_COMPACTION
3. Built-in compaction policy now uses a fixed built-in job template:
- builtin-iceberg-rewrite-data-files
### How was this patch tested?
Added/updated unit tests and ran targeted test suites:
- org.apache.gravitino.policy.TestPolicyBuiltInType
- org.apache.gravitino.policy.TestPolicyContents
- org.apache.gravitino.dto.policy.TestPolicyDTO
- org.apache.gravitino.meta.TestPolicyEntity
- org.apache.gravitino.server.web.rest.TestPolicyOperations
Executed with Gradle (targeted modules/tests), all passed
---
.../policy/IcebergDataCompactionContent.java | 280 +++++++++++++++++++++
.../java/org/apache/gravitino/policy/Policy.java | 9 +-
.../apache/gravitino/policy/PolicyContents.java | 61 +++++
.../gravitino/policy/TestPolicyBuiltInType.java | 54 ++++
.../gravitino/policy/TestPolicyContents.java | 120 +++++++++
.../gravitino/dto/policy/PolicyContentDTO.java | 130 ++++++++++
.../org/apache/gravitino/dto/policy/PolicyDTO.java | 3 +
.../dto/requests/PolicyCreateRequest.java | 3 +
.../dto/requests/PolicyUpdateRequest.java | 3 +
.../apache/gravitino/dto/util/DTOConverters.java | 26 ++
.../apache/gravitino/dto/policy/TestPolicyDTO.java | 64 +++++
.../org/apache/gravitino/meta/PolicyEntity.java | 6 +
.../storage/relational/utils/POConverters.java | 4 +-
.../apache/gravitino/meta/TestPolicyEntity.java | 38 +++
docs/iceberg-compaction-policy.md | 134 ++++++++++
docs/manage-policies-in-gravitino.md | 9 +-
.../compaction/CompactionStrategyHandler.java | 3 +-
.../recommender/strategy/GravitinoStrategy.java | 2 +-
.../compaction/CompactionStrategyForTest.java | 2 +-
.../compaction/TestCompactionStrategyHandler.java | 2 +-
.../TestGravitinoPolicyCompactionStrategy.java | 94 +++++++
.../server/web/rest/PolicyOperations.java | 19 +-
.../server/web/rest/TestPolicyOperations.java | 97 +++++++
23 files changed, 1146 insertions(+), 17 deletions(-)
diff --git
a/api/src/main/java/org/apache/gravitino/policy/IcebergDataCompactionContent.java
b/api/src/main/java/org/apache/gravitino/policy/IcebergDataCompactionContent.java
new file mode 100644
index 0000000000..50f76b538f
--- /dev/null
+++
b/api/src/main/java/org/apache/gravitino/policy/IcebergDataCompactionContent.java
@@ -0,0 +1,280 @@
+/*
+ * 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.gravitino.policy;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.MetadataObject;
+
+/** Built-in policy content for Iceberg compaction strategy. */
+public class IcebergDataCompactionContent implements PolicyContent {
+ /** Property key for strategy type. */
+ public static final String STRATEGY_TYPE_KEY = "strategy.type";
+ /** Strategy type value for iceberg data compaction. */
+ public static final String STRATEGY_TYPE_VALUE = "iceberg-data-compaction";
+ /** Property key for job template name. */
+ public static final String JOB_TEMPLATE_NAME_KEY = "job.template-name";
+ /** Built-in job template name for Iceberg rewrite data files. */
+ public static final String JOB_TEMPLATE_NAME_VALUE =
"builtin-iceberg-rewrite-data-files";
+ /** Prefix for rewrite options propagated to job options. */
+ public static final String JOB_OPTIONS_PREFIX = "job.options.";
+ /** Rule key for trigger expression. */
+ public static final String TRIGGER_EXPR_KEY = "trigger-expr";
+ /** Rule key for score expression. */
+ public static final String SCORE_EXPR_KEY = "score-expr";
+ /** Rule key for minimum data file MSE threshold. */
+ public static final String MIN_DATA_FILE_MSE_KEY = "minDataFileMse";
+ /** Rule key for minimum delete file count threshold. */
+ public static final String MIN_DELETE_FILE_NUMBER_KEY =
"minDeleteFileNumber";
+ /** Rule key for data file MSE score weight. */
+ public static final String DATA_FILE_MSE_WEIGHT_KEY = "dataFileMseWeight";
+ /** Rule key for delete file number score weight. */
+ public static final String DELETE_FILE_NUMBER_WEIGHT_KEY =
"deleteFileNumberWeight";
+ /** Rule key for max partition number selected for compaction. */
+ public static final String MAX_PARTITION_NUM_KEY = "max-partition-num";
+ /** Metric name for data file MSE. */
+ public static final String DATA_FILE_MSE_METRIC = "custom-data-file-mse";
+ /** Metric name for delete file number. */
+ public static final String DELETE_FILE_NUMBER_METRIC =
"custom-delete-file-number";
+ /** Default minimum threshold for data file MSE metric, equals (128 MiB *
0.15)^2. */
+ public static final long DEFAULT_MIN_DATA_FILE_MSE = 405323966463344L;
+ /** Default minimum threshold for delete file number metric. */
+ public static final long DEFAULT_MIN_DELETE_FILE_NUMBER = 1L;
+ /** Default score weight for data file MSE. */
+ public static final long DEFAULT_DATA_FILE_MSE_WEIGHT = 1L;
+ /** Default score weight for delete file number. */
+ public static final long DEFAULT_DELETE_FILE_NUMBER_WEIGHT = 100L;
+ /** Default max partition number for compaction. */
+ public static final long DEFAULT_MAX_PARTITION_NUM = 50L;
+ /** Default rewrite options for Iceberg rewrite data files. */
+ public static final Map<String, String> DEFAULT_REWRITE_OPTIONS =
ImmutableMap.of();
+
+ private static final Pattern OPTION_KEY_PATTERN =
Pattern.compile("[A-Za-z0-9._-]+");
+ private static final Set<MetadataObject.Type> SUPPORTED_OBJECT_TYPES =
+ ImmutableSet.of(
+ MetadataObject.Type.CATALOG, MetadataObject.Type.SCHEMA,
MetadataObject.Type.TABLE);
+ private static final String TRIGGER_EXPR =
+ DATA_FILE_MSE_METRIC
+ + " >= "
+ + MIN_DATA_FILE_MSE_KEY
+ + " || "
+ + DELETE_FILE_NUMBER_METRIC
+ + " >= "
+ + MIN_DELETE_FILE_NUMBER_KEY;
+ private static final String SCORE_EXPR =
+ DATA_FILE_MSE_METRIC
+ + " * "
+ + DATA_FILE_MSE_WEIGHT_KEY
+ + " + "
+ + DELETE_FILE_NUMBER_METRIC
+ + " * "
+ + DELETE_FILE_NUMBER_WEIGHT_KEY;
+
+ private final Long minDataFileMse;
+ private final Long minDeleteFileNumber;
+ private final Long dataFileMseWeight;
+ private final Long deleteFileNumberWeight;
+ private final Long maxPartitionNum;
+ private final Map<String, String> rewriteOptions;
+
+ /** Default constructor for Jackson deserialization only. */
+ private IcebergDataCompactionContent() {
+ this(null, null, null, null, null, null);
+ }
+
+ IcebergDataCompactionContent(
+ Long minDataFileMse,
+ Long minDeleteFileNumber,
+ Long dataFileMseWeight,
+ Long deleteFileNumberWeight,
+ Long maxPartitionNum,
+ Map<String, String> rewriteOptions) {
+ // Nullable inputs are treated as "use default" to simplify policy
creation.
+ this.minDataFileMse = minDataFileMse == null ? DEFAULT_MIN_DATA_FILE_MSE :
minDataFileMse;
+ this.minDeleteFileNumber =
+ minDeleteFileNumber == null ? DEFAULT_MIN_DELETE_FILE_NUMBER :
minDeleteFileNumber;
+ this.dataFileMseWeight =
+ dataFileMseWeight == null ? DEFAULT_DATA_FILE_MSE_WEIGHT :
dataFileMseWeight;
+ this.deleteFileNumberWeight =
+ deleteFileNumberWeight == null ? DEFAULT_DELETE_FILE_NUMBER_WEIGHT :
deleteFileNumberWeight;
+ this.maxPartitionNum = maxPartitionNum == null ? DEFAULT_MAX_PARTITION_NUM
: maxPartitionNum;
+ this.rewriteOptions =
+ rewriteOptions == null
+ ? DEFAULT_REWRITE_OPTIONS
+ : Collections.unmodifiableMap(new LinkedHashMap<>(rewriteOptions));
+ }
+
+ /**
+ * Returns the minimum threshold for {@value DATA_FILE_MSE_METRIC}.
+ *
+ * @return minimum data file MSE threshold
+ */
+ public Long minDataFileMse() {
+ return minDataFileMse;
+ }
+
+ /**
+ * Returns the minimum threshold for {@value DELETE_FILE_NUMBER_METRIC}.
+ *
+ * @return minimum delete file number threshold
+ */
+ public Long minDeleteFileNumber() {
+ return minDeleteFileNumber;
+ }
+
+ /**
+ * Returns the weight used by {@value DATA_FILE_MSE_METRIC} in score
expression.
+ *
+ * @return data file MSE score weight
+ */
+ public Long dataFileMseWeight() {
+ return dataFileMseWeight;
+ }
+
+ /**
+ * Returns the weight used by {@value DELETE_FILE_NUMBER_METRIC} in score
expression.
+ *
+ * @return delete file number score weight
+ */
+ public Long deleteFileNumberWeight() {
+ return deleteFileNumberWeight;
+ }
+
+ /**
+ * Returns max partition number selected for compaction.
+ *
+ * @return max partition number
+ */
+ public Long maxPartitionNum() {
+ return maxPartitionNum;
+ }
+
+ /**
+ * Returns rewrite options that are expanded to {@code job.options.*} rule
entries.
+ *
+ * @return rewrite options
+ */
+ public Map<String, String> rewriteOptions() {
+ return rewriteOptions;
+ }
+
+ @Override
+ public Set<MetadataObject.Type> supportedObjectTypes() {
+ return SUPPORTED_OBJECT_TYPES;
+ }
+
+ @Override
+ public Map<String, String> properties() {
+ // properties keep stable strategy/job identity; thresholds and scoring
knobs belong to rules.
+ return ImmutableMap.of(
+ STRATEGY_TYPE_KEY, STRATEGY_TYPE_VALUE, JOB_TEMPLATE_NAME_KEY,
JOB_TEMPLATE_NAME_VALUE);
+ }
+
+ @Override
+ public Map<String, Object> rules() {
+ Map<String, Object> rules = new LinkedHashMap<>();
+ rules.put(MIN_DATA_FILE_MSE_KEY, minDataFileMse);
+ rules.put(MIN_DELETE_FILE_NUMBER_KEY, minDeleteFileNumber);
+ rules.put(DATA_FILE_MSE_WEIGHT_KEY, dataFileMseWeight);
+ rules.put(DELETE_FILE_NUMBER_WEIGHT_KEY, deleteFileNumberWeight);
+ rules.put(MAX_PARTITION_NUM_KEY, maxPartitionNum);
+ rules.put(TRIGGER_EXPR_KEY, TRIGGER_EXPR);
+ rules.put(SCORE_EXPR_KEY, SCORE_EXPR);
+ rewriteOptions.forEach((key, value) -> rules.put(JOB_OPTIONS_PREFIX + key,
value));
+ return Collections.unmodifiableMap(rules);
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ PolicyContent.super.validate();
+ // All fields are defaulted in the constructor, so only range checks are
needed here.
+ Preconditions.checkArgument(minDataFileMse >= 0, "minDataFileMse must be
>= 0");
+ Preconditions.checkArgument(minDeleteFileNumber >= 0, "minDeleteFileNumber
must be >= 0");
+ Preconditions.checkArgument(dataFileMseWeight >= 0, "dataFileMseWeight
must be >= 0");
+ Preconditions.checkArgument(deleteFileNumberWeight >= 0,
"deleteFileNumberWeight must be >= 0");
+ Preconditions.checkArgument(maxPartitionNum > 0, "maxPartitionNum must be
> 0");
+
+ rewriteOptions.forEach(
+ (key, value) -> {
+ Preconditions.checkArgument(StringUtils.isNotBlank(key), "rewrite
option key is blank");
+ Preconditions.checkArgument(
+ OPTION_KEY_PATTERN.matcher(key).matches(),
+ "rewrite option key '%s' contains illegal characters",
+ key);
+ Preconditions.checkArgument(
+ !key.startsWith(JOB_OPTIONS_PREFIX),
+ "rewrite option key '%s' must not start with '%s'",
+ key,
+ JOB_OPTIONS_PREFIX);
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(value), "rewrite option '%s' must have
non-empty value", key);
+ });
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof IcebergDataCompactionContent)) {
+ return false;
+ }
+ IcebergDataCompactionContent that = (IcebergDataCompactionContent) o;
+ return Objects.equals(minDataFileMse, that.minDataFileMse)
+ && Objects.equals(minDeleteFileNumber, that.minDeleteFileNumber)
+ && Objects.equals(dataFileMseWeight, that.dataFileMseWeight)
+ && Objects.equals(deleteFileNumberWeight, that.deleteFileNumberWeight)
+ && Objects.equals(maxPartitionNum, that.maxPartitionNum)
+ && Objects.equals(rewriteOptions, that.rewriteOptions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ minDataFileMse,
+ minDeleteFileNumber,
+ dataFileMseWeight,
+ deleteFileNumberWeight,
+ maxPartitionNum,
+ rewriteOptions);
+ }
+
+ @Override
+ public String toString() {
+ return "IcebergDataCompactionContent{"
+ + "minDataFileMse="
+ + minDataFileMse
+ + ", minDeleteFileNumber="
+ + minDeleteFileNumber
+ + ", dataFileMseWeight="
+ + dataFileMseWeight
+ + ", deleteFileNumberWeight="
+ + deleteFileNumberWeight
+ + ", maxPartitionNum="
+ + maxPartitionNum
+ + ", rewriteOptions="
+ + rewriteOptions
+ + '}';
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/policy/Policy.java
b/api/src/main/java/org/apache/gravitino/policy/Policy.java
index 4f0e7cd1e0..7b27b01fcd 100644
--- a/api/src/main/java/org/apache/gravitino/policy/Policy.java
+++ b/api/src/main/java/org/apache/gravitino/policy/Policy.java
@@ -39,9 +39,9 @@ public interface Policy extends Auditable {
/** The built-in policy types. Predefined policy types that are provided by
the system. */
enum BuiltInType {
- // todo: add built-in policies, such as:
- // DATA_COMPACTION(BUILT_IN_TYPE_PREFIX + "data_compaction",
- // PolicyContent.DataCompactionContent.class)
+ /** Built-in policy type for Iceberg compaction strategy. */
+ ICEBERG_COMPACTION(
+ BUILT_IN_TYPE_PREFIX + "iceberg_compaction",
IcebergDataCompactionContent.class),
/**
* Custom policy type. "custom" is a fixed string that indicates the
policy is a non-built-in
@@ -61,7 +61,8 @@ public interface Policy extends Auditable {
* Get the built-in policy type from the policy type string.
*
* @param policyType the policy type string
- * @return the built-in policy type if it matches, otherwise returns
CUSTOM type
+ * @return the matched built-in policy type
+ * @throws IllegalArgumentException if the policy type is unknown or blank
*/
public static BuiltInType fromPolicyType(String policyType) {
Preconditions.checkArgument(StringUtils.isNotBlank(policyType),
"policyType cannot be blank");
diff --git a/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
b/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
index 5922136ba3..4b6acc9b94 100644
--- a/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
@@ -42,6 +42,67 @@ public class PolicyContents {
return new CustomContent(rules, supportedObjectTypes, properties);
}
+ /**
+ * Creates an iceberg compaction policy content with default values.
+ *
+ * @return iceberg compaction policy content with defaults
+ */
+ public static PolicyContent icebergDataCompaction() {
+ return icebergDataCompaction(
+ IcebergDataCompactionContent.DEFAULT_MIN_DATA_FILE_MSE,
+ IcebergDataCompactionContent.DEFAULT_MIN_DELETE_FILE_NUMBER,
+ IcebergDataCompactionContent.DEFAULT_DATA_FILE_MSE_WEIGHT,
+ IcebergDataCompactionContent.DEFAULT_DELETE_FILE_NUMBER_WEIGHT,
+ IcebergDataCompactionContent.DEFAULT_MAX_PARTITION_NUM,
+ IcebergDataCompactionContent.DEFAULT_REWRITE_OPTIONS);
+ }
+
+ /**
+ * Creates an iceberg compaction policy content.
+ *
+ * @param minDataFileMse minimum threshold for custom-data-file-mse
+ * @param minDeleteFileNumber minimum threshold for custom-delete-file-number
+ * @param rewriteOptions rewrite options forwarded as job.options.*
+ * @return iceberg compaction policy content
+ */
+ public static PolicyContent icebergDataCompaction(
+ long minDataFileMse, long minDeleteFileNumber, Map<String, String>
rewriteOptions) {
+ return icebergDataCompaction(
+ minDataFileMse,
+ minDeleteFileNumber,
+ IcebergDataCompactionContent.DEFAULT_DATA_FILE_MSE_WEIGHT,
+ IcebergDataCompactionContent.DEFAULT_DELETE_FILE_NUMBER_WEIGHT,
+ IcebergDataCompactionContent.DEFAULT_MAX_PARTITION_NUM,
+ rewriteOptions);
+ }
+
+ /**
+ * Creates an iceberg compaction policy content with configurable score
weights.
+ *
+ * @param minDataFileMse minimum threshold for custom-data-file-mse
+ * @param minDeleteFileNumber minimum threshold for custom-delete-file-number
+ * @param dataFileMseWeight weight used for custom-data-file-mse score
contribution
+ * @param deleteFileNumberWeight weight used for custom-delete-file-number
score contribution
+ * @param maxPartitionNum maximum partition number selected for compaction
+ * @param rewriteOptions rewrite options forwarded as job.options.*
+ * @return iceberg compaction policy content
+ */
+ public static PolicyContent icebergDataCompaction(
+ long minDataFileMse,
+ long minDeleteFileNumber,
+ long dataFileMseWeight,
+ long deleteFileNumberWeight,
+ long maxPartitionNum,
+ Map<String, String> rewriteOptions) {
+ return new IcebergDataCompactionContent(
+ minDataFileMse,
+ minDeleteFileNumber,
+ dataFileMseWeight,
+ deleteFileNumberWeight,
+ maxPartitionNum,
+ rewriteOptions);
+ }
+
private PolicyContents() {}
/**
diff --git
a/api/src/test/java/org/apache/gravitino/policy/TestPolicyBuiltInType.java
b/api/src/test/java/org/apache/gravitino/policy/TestPolicyBuiltInType.java
new file mode 100644
index 0000000000..68d2065cf5
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/policy/TestPolicyBuiltInType.java
@@ -0,0 +1,54 @@
+/*
+ * 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.gravitino.policy;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyBuiltInType {
+
+ @Test
+ void testFromPolicyTypeWithBuiltInTypeValue() {
+ Assertions.assertEquals(
+ Policy.BuiltInType.ICEBERG_COMPACTION,
+ Policy.BuiltInType.fromPolicyType("system_iceberg_compaction"));
+ }
+
+ @Test
+ void testFromPolicyTypeWithEnumName() {
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> Policy.BuiltInType.fromPolicyType("ICEBERG_COMPACTION"));
+ Assertions.assertTrue(exception.getMessage().contains("Unknown policy
type"));
+ }
+
+ @Test
+ void testBuiltInTypePolicyTypeValue() {
+ Assertions.assertEquals(
+ "system_iceberg_compaction",
Policy.BuiltInType.ICEBERG_COMPACTION.policyType());
+ }
+
+ @Test
+ void testBuiltInTypeContentClass() {
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.class,
Policy.BuiltInType.ICEBERG_COMPACTION.contentClass());
+ }
+}
diff --git
a/api/src/test/java/org/apache/gravitino/policy/TestPolicyContents.java
b/api/src/test/java/org/apache/gravitino/policy/TestPolicyContents.java
new file mode 100644
index 0000000000..702515d14b
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/policy/TestPolicyContents.java
@@ -0,0 +1,120 @@
+/*
+ * 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.gravitino.policy;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import org.apache.gravitino.MetadataObject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyContents {
+
+ @Test
+ void testIcebergCompactionContentUsesDefaults() {
+ IcebergDataCompactionContent content =
+ (IcebergDataCompactionContent) PolicyContents.icebergDataCompaction();
+
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.DEFAULT_MIN_DATA_FILE_MSE,
+ content.rules().get("minDataFileMse"));
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.DEFAULT_MIN_DELETE_FILE_NUMBER,
+ content.rules().get("minDeleteFileNumber"));
+ Assertions.assertEquals(1L, content.rules().get("dataFileMseWeight"));
+ Assertions.assertEquals(100L,
content.rules().get("deleteFileNumberWeight"));
+ Assertions.assertEquals(50L, content.rules().get("max-partition-num"));
+
Assertions.assertNull(content.rules().get("job.options.target-file-size-bytes"));
+ Assertions.assertNull(content.rules().get("job.options.min-input-files"));
+
Assertions.assertNull(content.rules().get("job.options.delete-file-threshold"));
+ }
+
+ @Test
+ void testIcebergCompactionContentGeneratesOptimizerFields() {
+ IcebergDataCompactionContent content =
+ (IcebergDataCompactionContent)
+ PolicyContents.icebergDataCompaction(
+ 1000L, 1L, Map.of("target-file-size-bytes", "1048576",
"min-input-files", "1"));
+
+ Assertions.assertEquals("iceberg-data-compaction",
content.properties().get("strategy.type"));
+ Assertions.assertEquals(
+ "builtin-iceberg-rewrite-data-files",
content.properties().get("job.template-name"));
+ Assertions.assertEquals(1000L, content.rules().get("minDataFileMse"));
+ Assertions.assertEquals(1L, content.rules().get("minDeleteFileNumber"));
+ Assertions.assertEquals(1L, content.rules().get("dataFileMseWeight"));
+ Assertions.assertEquals(100L,
content.rules().get("deleteFileNumberWeight"));
+ Assertions.assertEquals(50L, content.rules().get("max-partition-num"));
+ Assertions.assertEquals(
+ "custom-data-file-mse >= minDataFileMse || custom-delete-file-number
>= minDeleteFileNumber",
+ content.rules().get("trigger-expr"));
+ Assertions.assertEquals(
+ "custom-data-file-mse * dataFileMseWeight"
+ + " + custom-delete-file-number * deleteFileNumberWeight",
+ content.rules().get("score-expr"));
+ Assertions.assertEquals("1048576",
content.rules().get("job.options.target-file-size-bytes"));
+ Assertions.assertEquals("1",
content.rules().get("job.options.min-input-files"));
+ Assertions.assertEquals(
+ ImmutableSet.of(
+ MetadataObject.Type.CATALOG, MetadataObject.Type.SCHEMA,
MetadataObject.Type.TABLE),
+ content.supportedObjectTypes());
+ }
+
+ @Test
+ void testIcebergCompactionContentSupportsCustomWeights() {
+ IcebergDataCompactionContent content =
+ (IcebergDataCompactionContent)
+ PolicyContents.icebergDataCompaction(
+ 1000L,
+ 1L,
+ 3L,
+ 200L,
+ 88L,
+ Map.of("target-file-size-bytes", "1048576", "min-input-files",
"1"));
+
+ Assertions.assertEquals(3L, content.rules().get("dataFileMseWeight"));
+ Assertions.assertEquals(200L,
content.rules().get("deleteFileNumberWeight"));
+ Assertions.assertEquals(88L, content.rules().get("max-partition-num"));
+ Assertions.assertDoesNotThrow(content::validate);
+ }
+
+ @Test
+ void testIcebergCompactionContentRejectsInvalidRewriteOptionKey() {
+ IcebergDataCompactionContent content =
+ (IcebergDataCompactionContent)
+ PolicyContents.icebergDataCompaction(
+ 1000L, 1L, Map.of("job.options.target-file-size-bytes",
"1048576"));
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class,
content::validate);
+ Assertions.assertTrue(exception.getMessage().contains("must not start
with"));
+ }
+
+ @Test
+ void testIcebergCompactionContentRejectsInvalidMaxPartitionNum() {
+ IcebergDataCompactionContent content =
+ (IcebergDataCompactionContent)
+ PolicyContents.icebergDataCompaction(
+ 1000L, 1L, 2L, 10L, 0L, Map.of("target-file-size-bytes",
"1048576"));
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class,
content::validate);
+ Assertions.assertTrue(exception.getMessage().contains("maxPartitionNum"));
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
index 2de0cbeadd..c9d6cb0f4b 100644
--- a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
@@ -19,6 +19,8 @@
package org.apache.gravitino.dto.policy;
import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import lombok.AllArgsConstructor;
@@ -26,7 +28,9 @@ import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.policy.IcebergDataCompactionContent;
import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
/** Represents a Policy Content Data Transfer Object (DTO). */
public interface PolicyContentDTO extends PolicyContent {
@@ -69,4 +73,130 @@ public interface PolicyContentDTO extends PolicyContent {
return properties;
}
}
+
+ /** Represents a typed iceberg compaction policy content DTO. */
+ @EqualsAndHashCode
+ @ToString
+ @Builder(setterPrefix = "with")
+ @AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+ class IcebergCompactionContentDTO implements PolicyContentDTO {
+
+ @JsonProperty("minDataFileMse")
+ private Long minDataFileMse;
+
+ @JsonProperty("minDeleteFileNumber")
+ private Long minDeleteFileNumber;
+
+ @JsonProperty("dataFileMseWeight")
+ private Long dataFileMseWeight;
+
+ @JsonProperty("deleteFileNumberWeight")
+ private Long deleteFileNumberWeight;
+
+ @JsonProperty("maxPartitionNum")
+ private Long maxPartitionNum;
+
+ @JsonProperty("rewriteOptions")
+ private Map<String, String> rewriteOptions;
+
+ // Default constructor for Jackson deserialization only.
+ private IcebergCompactionContentDTO() {}
+
+ /**
+ * Returns the minimum threshold for custom-data-file-mse metric.
+ *
+ * @return minimum data file MSE threshold
+ */
+ public Long minDataFileMse() {
+ return minDataFileMse == null
+ ? IcebergDataCompactionContent.DEFAULT_MIN_DATA_FILE_MSE
+ : minDataFileMse;
+ }
+
+ /**
+ * Returns the minimum threshold for custom-delete-file-number metric.
+ *
+ * @return minimum delete file number threshold
+ */
+ public Long minDeleteFileNumber() {
+ return minDeleteFileNumber == null
+ ? IcebergDataCompactionContent.DEFAULT_MIN_DELETE_FILE_NUMBER
+ : minDeleteFileNumber;
+ }
+
+ /**
+ * Returns the weight for custom-data-file-mse metric in score expression.
+ *
+ * @return data file MSE score weight
+ */
+ public Long dataFileMseWeight() {
+ return dataFileMseWeight == null
+ ? IcebergDataCompactionContent.DEFAULT_DATA_FILE_MSE_WEIGHT
+ : dataFileMseWeight;
+ }
+
+ /**
+ * Returns the weight for custom-delete-file-number metric in score
expression.
+ *
+ * @return delete file number score weight
+ */
+ public Long deleteFileNumberWeight() {
+ return deleteFileNumberWeight == null
+ ? IcebergDataCompactionContent.DEFAULT_DELETE_FILE_NUMBER_WEIGHT
+ : deleteFileNumberWeight;
+ }
+
+ /**
+ * Returns max partition number selected for compaction.
+ *
+ * @return max partition number
+ */
+ public Long maxPartitionNum() {
+ return maxPartitionNum == null
+ ? IcebergDataCompactionContent.DEFAULT_MAX_PARTITION_NUM
+ : maxPartitionNum;
+ }
+
+ /**
+ * Returns rewrite options expanded to job.options.* during rule
generation.
+ *
+ * @return rewrite options map
+ */
+ public Map<String, String> rewriteOptions() {
+ return rewriteOptions == null
+ ? IcebergDataCompactionContent.DEFAULT_REWRITE_OPTIONS
+ : Collections.unmodifiableMap(new LinkedHashMap<>(rewriteOptions));
+ }
+
+ @Override
+ public Set<MetadataObject.Type> supportedObjectTypes() {
+ return toDomainContent().supportedObjectTypes();
+ }
+
+ @Override
+ public Map<String, String> properties() {
+ return toDomainContent().properties();
+ }
+
+ @Override
+ public Map<String, Object> rules() {
+ return toDomainContent().rules();
+ }
+
+ @Override
+ public void validate() throws IllegalArgumentException {
+ PolicyContentDTO.super.validate();
+ toDomainContent().validate();
+ }
+
+ private PolicyContent toDomainContent() {
+ return PolicyContents.icebergDataCompaction(
+ minDataFileMse(),
+ minDeleteFileNumber(),
+ dataFileMseWeight(),
+ deleteFileNumberWeight(),
+ maxPartitionNum(),
+ rewriteOptions());
+ }
+ }
}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
index d6c24dca24..51c1780879 100644
--- a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
@@ -55,6 +55,9 @@ public class PolicyDTO implements Policy {
// add mappings for built-in types here
// For example: @JsonSubTypes.Type(value = DataCompactionContent.class,
name =
// "system_data_compaction")
+ @JsonSubTypes.Type(
+ value = PolicyContentDTO.IcebergCompactionContentDTO.class,
+ name = "system_iceberg_compaction")
})
private PolicyContentDTO content;
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
index ba1f06ff30..158bbb3e43 100644
---
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
@@ -59,6 +59,9 @@ public class PolicyCreateRequest implements RESTRequest {
// add mappings for built-in types here
// For example: @JsonSubTypes.Type(value = DataCompactionContent.class,
name =
// "system_data_compaction")
+ @JsonSubTypes.Type(
+ value = PolicyContentDTO.IcebergCompactionContentDTO.class,
+ name = "system_iceberg_compaction")
})
private final PolicyContentDTO policyContent;
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
index 035ff10490..2cc53c68d0 100644
---
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
@@ -140,6 +140,9 @@ public interface PolicyUpdateRequest extends RESTRequest {
// add mappings for built-in types here
// For example: @JsonSubTypes.Type(value = DataCompactionContent.class,
name =
// "system_data_compaction")
+ @JsonSubTypes.Type(
+ value = PolicyContentDTO.IcebergCompactionContentDTO.class,
+ name = "system_iceberg_compaction")
})
private final PolicyContentDTO newContent;
diff --git
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index 16733b10c6..77e04605fe 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -97,6 +97,7 @@ import org.apache.gravitino.job.SparkJobTemplate;
import org.apache.gravitino.messaging.Topic;
import org.apache.gravitino.model.Model;
import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.policy.IcebergDataCompactionContent;
import org.apache.gravitino.policy.PolicyContent;
import org.apache.gravitino.policy.PolicyContents;
import org.apache.gravitino.rel.Column;
@@ -564,6 +565,19 @@ public class DTOConverters {
.build();
}
+ if (policyContent instanceof IcebergDataCompactionContent) {
+ IcebergDataCompactionContent icebergCompactionContent =
+ (IcebergDataCompactionContent) policyContent;
+ return PolicyContentDTO.IcebergCompactionContentDTO.builder()
+ .withMinDataFileMse(icebergCompactionContent.minDataFileMse())
+
.withMinDeleteFileNumber(icebergCompactionContent.minDeleteFileNumber())
+ .withDataFileMseWeight(icebergCompactionContent.dataFileMseWeight())
+
.withDeleteFileNumberWeight(icebergCompactionContent.deleteFileNumberWeight())
+ .withMaxPartitionNum(icebergCompactionContent.maxPartitionNum())
+ .withRewriteOptions(icebergCompactionContent.rewriteOptions())
+ .build();
+ }
+
throw new IllegalArgumentException("Unsupported policy content: " +
policyContent);
}
@@ -1297,6 +1311,18 @@ public class DTOConverters {
customContentDTO.properties());
}
+ if (policyContentDTO instanceof
PolicyContentDTO.IcebergCompactionContentDTO) {
+ PolicyContentDTO.IcebergCompactionContentDTO icebergCompactionContentDTO
=
+ (PolicyContentDTO.IcebergCompactionContentDTO) policyContentDTO;
+ return PolicyContents.icebergDataCompaction(
+ icebergCompactionContentDTO.minDataFileMse(),
+ icebergCompactionContentDTO.minDeleteFileNumber(),
+ icebergCompactionContentDTO.dataFileMseWeight(),
+ icebergCompactionContentDTO.deleteFileNumberWeight(),
+ icebergCompactionContentDTO.maxPartitionNum(),
+ icebergCompactionContentDTO.rewriteOptions());
+ }
+
throw new IllegalArgumentException(
"Unsupported policy content type: " + policyContentDTO.getClass());
}
diff --git
a/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
b/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
index f1c2aeb7a1..7bbe897cfc 100644
--- a/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
+++ b/common/src/test/java/org/apache/gravitino/dto/policy/TestPolicyDTO.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.dto.AuditDTO;
import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.policy.IcebergDataCompactionContent;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -107,4 +108,67 @@ public class TestPolicyDTO {
PolicyDTO deserPolicyDTO4 = JsonUtils.objectMapper().readValue(serJson,
PolicyDTO.class);
Assertions.assertEquals(Optional.of(true), deserPolicyDTO4.inherited());
}
+
+ @Test
+ public void testIcebergCompactionPolicySerDe() throws
JsonProcessingException {
+ AuditDTO audit =
AuditDTO.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+ PolicyContentDTO.IcebergCompactionContentDTO typedContent =
+ PolicyContentDTO.IcebergCompactionContentDTO.builder()
+ .withMinDataFileMse(1000L)
+ .withMinDeleteFileNumber(1L)
+ .withDataFileMseWeight(2L)
+ .withDeleteFileNumberWeight(150L)
+ .withMaxPartitionNum(99L)
+ .withRewriteOptions(
+ ImmutableMap.of("target-file-size-bytes", "1048576",
"min-input-files", "1"))
+ .build();
+
+ PolicyDTO policyDTO =
+ PolicyDTO.builder()
+ .withName("iceberg-compaction")
+ .withComment("typed policy")
+ .withPolicyType("system_iceberg_compaction")
+ .withEnabled(true)
+ .withContent(typedContent)
+ .withAudit(audit)
+ .build();
+
+ String serJson = JsonUtils.objectMapper().writeValueAsString(policyDTO);
+ PolicyDTO deserPolicyDTO = JsonUtils.objectMapper().readValue(serJson,
PolicyDTO.class);
+
+ Assertions.assertEquals(policyDTO, deserPolicyDTO);
+ Assertions.assertInstanceOf(
+ PolicyContentDTO.IcebergCompactionContentDTO.class,
deserPolicyDTO.content());
+ }
+
+ @Test
+ public void testIcebergCompactionPolicyDefaultValues() throws
JsonProcessingException {
+ String json =
+ "{"
+ + "\"name\":\"iceberg-compaction-default\","
+ + "\"comment\":\"typed policy\","
+ + "\"policyType\":\"system_iceberg_compaction\","
+ + "\"enabled\":true,"
+ + "\"content\":{}"
+ + "}";
+
+ PolicyDTO policyDTO = JsonUtils.objectMapper().readValue(json,
PolicyDTO.class);
+ PolicyContentDTO.IcebergCompactionContentDTO contentDTO =
+ (PolicyContentDTO.IcebergCompactionContentDTO) policyDTO.content();
+
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.DEFAULT_MIN_DATA_FILE_MSE,
contentDTO.minDataFileMse());
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.DEFAULT_MIN_DELETE_FILE_NUMBER,
+ contentDTO.minDeleteFileNumber());
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.DEFAULT_DATA_FILE_MSE_WEIGHT,
contentDTO.dataFileMseWeight());
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.DEFAULT_DELETE_FILE_NUMBER_WEIGHT,
+ contentDTO.deleteFileNumberWeight());
+ Assertions.assertEquals(
+ IcebergDataCompactionContent.DEFAULT_MAX_PARTITION_NUM,
contentDTO.maxPartitionNum());
+ Assertions.assertTrue(contentDTO.rewriteOptions().isEmpty());
+ Assertions.assertDoesNotThrow(contentDTO::validate);
+ }
}
diff --git a/core/src/main/java/org/apache/gravitino/meta/PolicyEntity.java
b/core/src/main/java/org/apache/gravitino/meta/PolicyEntity.java
index 6a1070489f..2c122c3ca8 100644
--- a/core/src/main/java/org/apache/gravitino/meta/PolicyEntity.java
+++ b/core/src/main/java/org/apache/gravitino/meta/PolicyEntity.java
@@ -163,6 +163,12 @@ public class PolicyEntity implements Entity, Auditable,
HasIdentifier {
|| content() instanceof PolicyContents.CustomContent,
"Expected CustomContent for custom policy type, but got %s",
content().getClass().getName());
+ Preconditions.checkArgument(
+ policyType == Policy.BuiltInType.CUSTOM ||
policyType.contentClass().isInstance(content()),
+ "Expected %s for policy type %s, but got %s",
+ policyType.contentClass().getSimpleName(),
+ policyType.name(),
+ content().getClass().getName());
content().validate();
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
index 95344a8f89..71ce8b42a2 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
@@ -820,7 +820,7 @@ public class POConverters {
return PolicyPO.builder()
.withPolicyId(newPolicy.id())
.withPolicyName(newPolicy.name())
- .withPolicyType(newPolicy.policyType().name())
+ .withPolicyType(newPolicy.policyType().policyType())
.withMetalakeId(oldPolicyPO.getMetalakeId())
.withAuditInfo(JsonUtils.anyFieldMapper().writeValueAsString(newPolicy.auditInfo()))
.withCurrentVersion(currentVersion)
@@ -1512,7 +1512,7 @@ public class POConverters {
return builder
.withPolicyId(policyEntity.id())
.withPolicyName(policyEntity.name())
- .withPolicyType(policyEntity.policyType().name())
+ .withPolicyType(policyEntity.policyType().policyType())
.withAuditInfo(JsonUtils.anyFieldMapper().writeValueAsString(policyEntity.auditInfo()))
.withCurrentVersion(INIT_VERSION)
.withLastVersion(INIT_VERSION)
diff --git a/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
b/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
index 94fd8a86cb..5152d14429 100644
--- a/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
+++ b/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
@@ -117,4 +117,42 @@ public class TestPolicyEntity {
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
.build());
}
+
+ @Test
+ public void testBuiltInPolicyContentTypeValidation() {
+ Namespace namespace = Namespace.of("m1", "c1", "s1");
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build();
+
+ PolicyContent icebergCompactionContent =
+ PolicyContents.icebergDataCompaction(
+ 1000L, 1L, Map.of("target-file-size-bytes", "1048576"));
+ Assertions.assertDoesNotThrow(
+ () ->
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("iceberg-compaction")
+ .withNamespace(namespace)
+ .withPolicyType(Policy.BuiltInType.ICEBERG_COMPACTION)
+ .withEnabled(true)
+ .withContent(icebergCompactionContent)
+ .withAuditInfo(auditInfo)
+ .build());
+
+ PolicyContent customContent =
+ PolicyContents.custom(
+ ImmutableMap.of("k", "v"),
ImmutableSet.of(MetadataObject.Type.TABLE), Map.of());
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("iceberg-compaction")
+ .withNamespace(namespace)
+ .withPolicyType(Policy.BuiltInType.ICEBERG_COMPACTION)
+ .withEnabled(true)
+ .withContent(customContent)
+ .withAuditInfo(auditInfo)
+ .build());
+ }
}
diff --git a/docs/iceberg-compaction-policy.md
b/docs/iceberg-compaction-policy.md
new file mode 100644
index 0000000000..b68edf0511
--- /dev/null
+++ b/docs/iceberg-compaction-policy.md
@@ -0,0 +1,134 @@
+---
+title: "Iceberg compaction policy"
+slug: /iceberg-compaction-policy
+date: 2026-03-05
+keyword: iceberg, compaction, policy, optimizer, Gravitino
+license: This software is licensed under the Apache License version 2.
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Overview
+
+`system_iceberg_compaction` is a built-in policy type used by the optimizer to
generate compaction strategies and job contexts for Iceberg tables.
+
+This policy supports `CATALOG`, `SCHEMA`, and `TABLE` metadata objects.
+
+## Policy content
+
+The typed content for `system_iceberg_compaction` supports the following
fields:
+
+| Field | Required | Default | Description |
+|---|---|---|---|
+| `minDataFileMse` | No | `405323966463344` | Minimum threshold for metric
`custom-data-file-mse`. Must be `>= 0`. |
+| `minDeleteFileNumber` | No | `1` | Minimum threshold for metric
`custom-delete-file-number`. Must be `>= 0`. |
+| `dataFileMseWeight` | No | `1` | Score weight of `custom-data-file-mse`.
Must be `>= 0`. |
+| `deleteFileNumberWeight` | No | `100` | Score weight of
`custom-delete-file-number`. Must be `>= 0`. |
+| `maxPartitionNum` | No | `50` | Maximum number of partitions selected by
optimizer. Must be `> 0`. |
+| `rewriteOptions` | No | `{}` | Additional rewrite options, expanded as
`job.options.*` rules. |
+
+## Generated rules and properties
+
+The policy content is converted to:
+
+- Properties:
+ - `strategy.type=iceberg-data-compaction`
+ - `job.template-name=builtin-iceberg-rewrite-data-files`
+- Rules:
+ - `trigger-expr=custom-data-file-mse >= minDataFileMse ||
custom-delete-file-number >= minDeleteFileNumber`
+ - `score-expr=custom-data-file-mse * dataFileMseWeight +
custom-delete-file-number * deleteFileNumberWeight`
+ - `max-partition-num=<maxPartitionNum>`
+ - `job.options.<key>=<value>` for each rewrite option
+
+## Parameter tuning guide
+
+### Metric unit and threshold formula
+
+`custom-data-file-mse` is expected to be in `byte^2`.
+
+Use the target file size and a tolerance ratio to set `minDataFileMse`:
+
+`minDataFileMse = (target-file-size-bytes * ratio)^2`
+
+Recommended `ratio` range: `0.1` to `0.2`.
+
+Default values use:
+
+- `target-file-size-bytes = 134217728` (128 MiB)
+- `ratio = 0.15`
+- `minDataFileMse = 405323966463344`
+
+### Trigger behavior
+
+The trigger expression uses `>=`.
+
+- Set `minDeleteFileNumber = 1` to trigger when at least one delete file
exists.
+- Set `minDeleteFileNumber > 1` to reduce compaction frequency for delete
files.
+
+### Score weights
+
+Score is computed as:
+
+`custom-data-file-mse * dataFileMseWeight + custom-delete-file-number *
deleteFileNumberWeight`
+
+- Keep `dataFileMseWeight = 1` as baseline.
+- Increase `deleteFileNumberWeight` if you want partitions with more delete
files to be prioritized.
+- Keep both weights non-negative.
+
+### Recommended defaults for production start
+
+- `minDataFileMse = 405323966463344` (computed from 128 MiB and ratio `0.15`)
+- `minDeleteFileNumber = 1`
+- `dataFileMseWeight = 1`
+- `deleteFileNumberWeight = 100`
+- `maxPartitionNum = 50`
+
+Recommended `rewriteOptions`:
+
+- `target-file-size-bytes = 134217728`
+- `min-input-files = 5`
+- `delete-file-threshold = 1`
+
+## Create policy examples
+
+<Tabs groupId='language' queryString>
+<TabItem value="shell" label="Shell">
+
+```shell
+curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "iceberg_compaction_default",
+ "comment": "Built-in iceberg compaction policy",
+ "policyType": "system_iceberg_compaction",
+ "enabled": true,
+ "content": {}
+ }' \
+ http://localhost:8090/api/metalakes/test/policies
+```
+
+</TabItem>
+<TabItem value="java" label="Java">
+
+```java
+GravitinoClient client = ...;
+
+PolicyContent content = PolicyContents.icebergDataCompaction();
+
+Policy policy =
+ client.createPolicy(
+ "iceberg_compaction_default",
+ "system_iceberg_compaction",
+ "Built-in iceberg compaction policy",
+ true,
+ content);
+```
+
+</TabItem>
+</Tabs>
+
+## Attach policy to metadata objects
+
+After the policy is created, associate it with a catalog, schema, or table
through standard policy association APIs.
+The optimizer will read the generated rules and properties to evaluate
strategy triggering and job submission context.
diff --git a/docs/manage-policies-in-gravitino.md
b/docs/manage-policies-in-gravitino.md
index b4c6c7f1b7..5eb848780f 100644
--- a/docs/manage-policies-in-gravitino.md
+++ b/docs/manage-policies-in-gravitino.md
@@ -42,8 +42,8 @@ Javadoc and REST API documentation.
The first step to managing policies is to create new policies. You can create
a new policy by providing a policy
name, type, and other optional fields like comment, enabled, etc.
-Gravitino supports two kinds of policies: built-in policies and custom
policies.
-For built-in policies, the `policyType` starts with `system.` and the
`supportedObjectTypes` in the policy content is predefined.
+Gravitino supports two kinds of policies: built-in policies and custom
policies.
+For built-in policies, the `policyType` starts with `system_` and the
`supportedObjectTypes` in the policy content is predefined.
For custom policies, the `policyType` must be `custom` and the
`supportedObjectTypes` can be any combination of metadata object types.
:::note
@@ -104,6 +104,10 @@ Policy policy = client.createPolicy(
</TabItem>
</Tabs>
+### Built-in Iceberg compaction policy
+
+For the built-in `system_iceberg_compaction` policy content, field
definitions, and examples, see [Iceberg compaction
policy](./iceberg-compaction-policy.md).
+
### List created policies
You can list all the created policy names as well as policy objects in a
metalake in Gravitino.
@@ -465,4 +469,3 @@ int count = policy.associatedObjects().count();
</TabItem>
</Tabs>
-
diff --git
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyHandler.java
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyHandler.java
index 774e2a8119..5a5c66d65e 100644
---
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyHandler.java
+++
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyHandler.java
@@ -28,6 +28,7 @@ import
org.apache.gravitino.maintenance.optimizer.api.common.PartitionPath;
import org.apache.gravitino.maintenance.optimizer.api.common.Strategy;
import
org.apache.gravitino.maintenance.optimizer.api.recommender.JobExecutionContext;
import
org.apache.gravitino.maintenance.optimizer.recommender.handler.BaseExpressionStrategyHandler;
+import org.apache.gravitino.policy.IcebergDataCompactionContent;
import org.apache.gravitino.rel.Table;
/**
@@ -35,7 +36,7 @@ import org.apache.gravitino.rel.Table;
*/
public class CompactionStrategyHandler extends BaseExpressionStrategyHandler {
- public static final String NAME = "compaction";
+ public static final String NAME =
IcebergDataCompactionContent.STRATEGY_TYPE_VALUE;
@Override
public Set<DataRequirement> dataRequirements() {
diff --git
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/strategy/GravitinoStrategy.java
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/strategy/GravitinoStrategy.java
index a66bb43b7a..e0708bdb12 100644
---
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/strategy/GravitinoStrategy.java
+++
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/recommender/strategy/GravitinoStrategy.java
@@ -43,7 +43,7 @@ public class GravitinoStrategy implements PartitionStrategy {
/** Rule key for the partition table score aggregation mode. */
public static final String PARTITION_TABLE_SCORE_MODE =
"partition_table_score_mode";
/** Rule key for the maximum number of partitions selected for execution. */
- public static final String MAX_PARTITION_NUM = "max_partition_num";
+ public static final String MAX_PARTITION_NUM = "max-partition-num";
private static final int DEFAULT_MAX_PARTITION_NUM = 100;
diff --git
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyForTest.java
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyForTest.java
index 1934b0f215..04be5fc21d 100644
---
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyForTest.java
+++
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/CompactionStrategyForTest.java
@@ -35,7 +35,7 @@ public class CompactionStrategyForTest implements Strategy {
@Override
public String strategyType() {
- return "compaction";
+ return CompactionStrategyHandler.NAME;
}
@Override
diff --git
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/TestCompactionStrategyHandler.java
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/TestCompactionStrategyHandler.java
index a236554fa4..231204b696 100644
---
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/TestCompactionStrategyHandler.java
+++
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/TestCompactionStrategyHandler.java
@@ -435,7 +435,7 @@ class TestCompactionStrategyHandler {
@Override
public String strategyType() {
- return "compaction";
+ return CompactionStrategyHandler.NAME;
}
@Override
diff --git
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/TestGravitinoPolicyCompactionStrategy.java
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/TestGravitinoPolicyCompactionStrategy.java
new file mode 100644
index 0000000000..f36c28702e
--- /dev/null
+++
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/recommender/handler/compaction/TestGravitinoPolicyCompactionStrategy.java
@@ -0,0 +1,94 @@
+/*
+ * 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.gravitino.maintenance.optimizer.recommender.handler.compaction;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.maintenance.optimizer.api.common.StatisticEntry;
+import
org.apache.gravitino.maintenance.optimizer.api.recommender.StrategyEvaluation;
+import
org.apache.gravitino.maintenance.optimizer.api.recommender.StrategyHandlerContext;
+import org.apache.gravitino.maintenance.optimizer.common.StatisticEntryImpl;
+import
org.apache.gravitino.maintenance.optimizer.recommender.strategy.GravitinoStrategy;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.expressions.transforms.Transform;
+import org.apache.gravitino.stats.StatisticValues;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class TestGravitinoPolicyCompactionStrategy {
+
+ @Test
+ void testPolicyGeneratesExpectedStrategyAndJobContext() {
+ PolicyContent content =
+ PolicyContents.icebergDataCompaction(
+ 1000L,
+ 1L,
+ 2L,
+ 10L,
+ 20L,
+ Map.of("target-file-size-bytes", "1048576", "min-input-files",
"4"));
+ Policy policy = Mockito.mock(Policy.class);
+ Mockito.when(policy.name()).thenReturn("iceberg-compaction-policy");
+ Mockito.when(policy.content()).thenReturn(content);
+
+ GravitinoStrategy strategy = new GravitinoStrategy(policy);
+ Assertions.assertEquals(CompactionStrategyHandler.NAME,
strategy.strategyType());
+ Assertions.assertEquals("builtin-iceberg-rewrite-data-files",
strategy.jobTemplateName());
+ Assertions.assertEquals(
+ Map.of("target-file-size-bytes", "1048576", "min-input-files", "4"),
strategy.jobOptions());
+
+ NameIdentifier tableId = NameIdentifier.of("db", "table");
+ Table tableMetadata = Mockito.mock(Table.class);
+ Mockito.when(tableMetadata.partitioning()).thenReturn(new Transform[0]);
+ Mockito.when(tableMetadata.columns()).thenReturn(new Column[0]);
+ List<StatisticEntry<?>> tableStatistics =
+ List.of(
+ new StatisticEntryImpl("custom-data-file-mse",
StatisticValues.longValue(3000L)),
+ new StatisticEntryImpl("custom-delete-file-number",
StatisticValues.longValue(5L)));
+
+ StrategyHandlerContext context =
+ StrategyHandlerContext.builder(tableId, strategy)
+ .withTableMetadata(tableMetadata)
+ .withTableStatistics(tableStatistics)
+ .build();
+
+ CompactionStrategyHandler handler = new CompactionStrategyHandler();
+ handler.initialize(context);
+
+ Assertions.assertTrue(handler.shouldTrigger());
+
+ StrategyEvaluation evaluation = handler.evaluate();
+ Assertions.assertEquals(6050L, evaluation.score());
+ CompactionJobContext jobContext =
+ (CompactionJobContext) evaluation.jobExecutionContext().orElseThrow();
+ Assertions.assertEquals(tableId, jobContext.nameIdentifier());
+ Assertions.assertEquals("builtin-iceberg-rewrite-data-files",
jobContext.jobTemplateName());
+ Assertions.assertEquals(
+ Map.of("target-file-size-bytes", "1048576", "min-input-files", "4"),
+ jobContext.jobOptions());
+ Assertions.assertTrue(jobContext.getPartitions().isEmpty());
+ }
+}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
index e1202e5c18..5e73e91236 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
@@ -23,7 +23,6 @@ import static
org.apache.gravitino.dto.util.DTOConverters.fromDTO;
import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
import java.util.Arrays;
-import java.util.Locale;
import java.util.Optional;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
@@ -149,6 +148,7 @@ public class PolicyOperations {
httpRequest,
() -> {
request.validate();
+ validateCreatePolicyType(request.getPolicyType());
PolicyEntity policy =
policyDispatcher.createPolicy(
metalake,
@@ -334,17 +334,28 @@ public class PolicyOperations {
}
static PolicyDTO toDTO(PolicyEntity policy, Optional<Boolean> inherited) {
+ String policyType = policy.policyType().policyType();
PolicyDTO.Builder builder =
PolicyDTO.builder()
.withName(policy.name())
.withComment(policy.comment())
-
.withPolicyType(policy.policyType().name().toLowerCase(Locale.ROOT))
+ .withPolicyType(policyType)
.withEnabled(policy.enabled())
.withContent(DTOConverters.toDTO(policy.content()))
.withInherited(inherited)
- .withAudit(DTOConverters.toDTO(policy.auditInfo()))
- .withInherited(inherited);
+ .withAudit(DTOConverters.toDTO(policy.auditInfo()));
return builder.build();
}
+
+ private static void validateCreatePolicyType(String policyType) {
+ Policy.BuiltInType builtInType =
Policy.BuiltInType.fromPolicyType(policyType);
+ if (builtInType != Policy.BuiltInType.CUSTOM
+ && !builtInType.policyType().equalsIgnoreCase(policyType)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Built-in policy type must use prefixed value '%s', but got: %s",
+ builtInType.policyType(), policyType));
+ }
+ }
}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
index 5788583b5a..af8056b666 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
@@ -44,6 +44,7 @@ import org.apache.gravitino.Config;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.dto.policy.PolicyContentDTO;
import org.apache.gravitino.dto.requests.PolicyCreateRequest;
import org.apache.gravitino.dto.requests.PolicySetRequest;
import org.apache.gravitino.dto.requests.PolicyUpdateRequest;
@@ -299,6 +300,7 @@ public class TestPolicyOperations extends
BaseOperationsTest {
Policy respPolicy = policyResp.getPolicy();
Assertions.assertEquals(policy1.name(), respPolicy.name());
Assertions.assertEquals(policy1.comment(), respPolicy.comment());
+ Assertions.assertEquals("custom", respPolicy.policyType());
Assertions.assertEquals(Optional.empty(), respPolicy.inherited());
// Test throw PolicyAlreadyExistsException
@@ -337,6 +339,101 @@ public class TestPolicyOperations extends
BaseOperationsTest {
Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp1.getType());
}
+ @Test
+ public void testCreatePolicyWithIcebergCompactionType() {
+ PolicyContent content =
+ PolicyContents.icebergDataCompaction(
+ 1000L,
+ 1L,
+ ImmutableMap.of("target-file-size-bytes", "1048576",
"min-input-files", "1"));
+ PolicyEntity policy1 =
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("iceberg-compaction")
+ .withPolicyType(Policy.BuiltInType.ICEBERG_COMPACTION)
+ .withEnabled(true)
+ .withContent(content)
+ .withAuditInfo(testAuditInfo1)
+ .build();
+ when(policyManager.createPolicy(
+ metalake,
+ "iceberg-compaction",
+ Policy.BuiltInType.ICEBERG_COMPACTION,
+ null,
+ true,
+ content))
+ .thenReturn(policy1);
+
+ PolicyCreateRequest request =
+ new PolicyCreateRequest(
+ "iceberg-compaction", "system_iceberg_compaction", null, true,
toDTO(content));
+ Response resp =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+ Assertions.assertEquals(0, policyResp.getCode());
+ Assertions.assertEquals("system_iceberg_compaction",
policyResp.getPolicy().policyType());
+ }
+
+ @Test
+ public void testCreatePolicyWithIcebergCompactionTypeDefaults() {
+ PolicyContent content = PolicyContents.icebergDataCompaction();
+ PolicyEntity policy =
+ PolicyEntity.builder()
+ .withId(1L)
+ .withName("iceberg-compaction-default")
+ .withPolicyType(Policy.BuiltInType.ICEBERG_COMPACTION)
+ .withEnabled(true)
+ .withContent(content)
+ .withAuditInfo(testAuditInfo1)
+ .build();
+ when(policyManager.createPolicy(
+ metalake,
+ "iceberg-compaction-default",
+ Policy.BuiltInType.ICEBERG_COMPACTION,
+ null,
+ true,
+ content))
+ .thenReturn(policy);
+
+ PolicyContentDTO.IcebergCompactionContentDTO minimalContent =
+ PolicyContentDTO.IcebergCompactionContentDTO.builder().build();
+ PolicyCreateRequest request =
+ new PolicyCreateRequest(
+ "iceberg-compaction-default", "system_iceberg_compaction", null,
true, minimalContent);
+
+ Response resp =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ PolicyResponse policyResp = resp.readEntity(PolicyResponse.class);
+ Assertions.assertEquals(0, policyResp.getCode());
+ Assertions.assertEquals("system_iceberg_compaction",
policyResp.getPolicy().policyType());
+ }
+
+ @Test
+ public void testCreatePolicyWithIcebergCompactionEnumTypeRejected() {
+ PolicyContent content = PolicyContents.icebergDataCompaction();
+ PolicyCreateRequest request =
+ new PolicyCreateRequest(
+ "iceberg-compaction", "ICEBERG_COMPACTION", null, true,
toDTO(content));
+
+ Response resp =
+ target(policyPath(metalake))
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(),
resp.getStatus());
+ }
+
@Test
public void testGetPolicy() {
ImmutableMap<String, Object> contentFields =
ImmutableMap.of("target_file_size_bytes", 1000);