This is an automated email from the ASF dual-hosted git repository.
roryqi 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 02cafdb0da [#12163] feat(api): Add tag assignment value APIs (#12162)
02cafdb0da is described below
commit 02cafdb0da00b3949193d68476d7414aa8648c85
Author: roryqi <[email protected]>
AuthorDate: Tue Jul 28 16:40:03 2026 +0800
[#12163] feat(api): Add tag assignment value APIs (#12162)
### What changes were proposed in this pull request?
Add the public tag assignment value APIs with `TagValue`, tag allowed
values, and valued tag association overloads.
### Why are the changes needed?
This is the API layer for supporting multiple assignment values for a
tag.
Fix: #12163
### Does this PR introduce _any_ user-facing change?
Yes. It adds `TagValue`, exposes tag allowed values, and adds valued tag
association APIs while keeping existing valueless tag association APIs.
### How was this patch tested?
`./gradlew :api:spotlessApply`
`./gradlew :api:test --tests org.apache.gravitino.tag.TestTagValue
-PskipITs -PskipDockerTests=false`
---
.../org/apache/gravitino/tag/SupportsTags.java | 29 ++--
.../main/java/org/apache/gravitino/tag/Tag.java | 30 ++++
.../org/apache/gravitino/tag/TagAssignment.java | 113 +++++++++++++++
.../org/apache/gravitino/tag/TagOperations.java | 25 ++++
.../java/org/apache/gravitino/tag/TagValue.java | 114 +++++++++++++++
.../apache/gravitino/tag/TagValueConstraint.java | 158 +++++++++++++++++++++
.../apache/gravitino/tag/TestTagAssignment.java | 69 +++++++++
.../org/apache/gravitino/tag/TestTagValue.java | 86 +++++++++++
.../gravitino/tag/TestTagValueConstraint.java | 89 ++++++++++++
9 files changed, 705 insertions(+), 8 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
b/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
index 0a909c4c8c..5d75cd139d 100644
--- a/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
+++ b/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
@@ -54,17 +54,30 @@ public interface SupportsTags {
Tag getTag(String name) throws NoSuchTagException;
/**
- * Associate tags to the specific object. The tagsToAdd will be added to the
object, and the
- * tagsToRemove will be removed from the object. Note that: 1) Adding or
removing tags that are
- * not existed will be ignored. 2) If the same name tag is in both tagsToAdd
and tagsToRemove, it
- * will be ignored. 3) If the tag is already associated with the object, it
will throw {@link
- * TagAlreadyAssociatedException}
+ * Associate tags with no value to the specific object. The tagsToAdd will
be added to the object,
+ * and the tagsToRemove will be removed from the object. Missing tags are
ignored. If the same tag
+ * is in both tagsToAdd and tagsToRemove, it will be ignored. Repeated
existing assignments are
+ * idempotent.
*
- * @param tagsToAdd The arrays of tag name to be added to the object.
- * @param tagsToRemove The array of tag name to be removed from the object.
+ * @param tagsToAdd The array of tag names to be added to the object.
+ * @param tagsToRemove The array of tag names to be removed from the object.
* @return The array of tag names that are associated with the object.
- * @throws TagAlreadyAssociatedException If the tag is already associated
with the object.
+ * @throws TagAlreadyAssociatedException If the association cannot be
applied.
*/
String[] associateTags(String[] tagsToAdd, String[] tagsToRemove)
throws TagAlreadyAssociatedException;
+
+ /**
+ * Associate tag values to the specific object.
+ *
+ * @param tagsToAdd The tag values to be added to the object.
+ * @param tagsToRemove The tag values to be removed from the object.
+ * @return The array of tag names that are associated with the object.
+ * @throws TagAlreadyAssociatedException If the association cannot be
applied.
+ */
+ default String[] associateTags(TagValue[] tagsToAdd, TagValue[] tagsToRemove)
+ throws TagAlreadyAssociatedException {
+ throw new UnsupportedOperationException(
+ "The associateTags(TagValue[], TagValue[]) method is not supported.");
+ }
}
diff --git a/api/src/main/java/org/apache/gravitino/tag/Tag.java
b/api/src/main/java/org/apache/gravitino/tag/Tag.java
index c2c107dca6..f17d877e07 100644
--- a/api/src/main/java/org/apache/gravitino/tag/Tag.java
+++ b/api/src/main/java/org/apache/gravitino/tag/Tag.java
@@ -54,6 +54,26 @@ public interface Tag extends Auditable {
*/
Map<String, String> properties();
+ /**
+ * @return The assignment value constraint of the tag. The default means the
tag has no value
+ * restriction and may be assigned without values or with any non-empty
assignment value.
+ */
+ default TagValueConstraint valueConstraint() {
+ return TagValueConstraint.anyValue();
+ }
+
+ /**
+ * Returns the tag assignment in the current metadata-object context.
+ *
+ * <p>The return value is present only when the tag is returned for a
metadata object. A present
+ * assignment without values means the tag is directly or effectively
assigned without values.
+ *
+ * @return The assignment in context, or empty when no assignment context
exists.
+ */
+ default Optional<TagAssignment> assignment() {
+ return Optional.empty();
+ }
+
/**
* Check if the tag is inherited from a parent object or not. If the tag is
inherited, it will
* return true, if it is owned by the object itself, it will return false.
@@ -88,5 +108,15 @@ public interface Tag extends Auditable {
* @return The list of objects that are associated with this tag.
*/
MetadataObject[] objects();
+
+ /**
+ * List objects associated with this tag and assignment value.
+ *
+ * @param value The exact assignment value to filter by.
+ * @return The list of objects that are associated with this tag and value.
+ */
+ default MetadataObject[] objects(String value) {
+ throw new UnsupportedOperationException("The objects(value) method is
not supported.");
+ }
}
}
diff --git a/api/src/main/java/org/apache/gravitino/tag/TagAssignment.java
b/api/src/main/java/org/apache/gravitino/tag/TagAssignment.java
new file mode 100644
index 0000000000..7f350807a5
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/tag/TagAssignment.java
@@ -0,0 +1,113 @@
+/*
+ * 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.tag;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.annotation.Evolving;
+
+/** Represents a tag assignment in a metadata-object context. */
+@Evolving
+public final class TagAssignment {
+
+ private static final TagAssignment NO_VALUE = new TagAssignment(new
String[0]);
+
+ private final String[] values;
+
+ private TagAssignment(String[] values) {
+ this.values = values.clone();
+ }
+
+ /**
+ * Creates an assignment with no value.
+ *
+ * @return The tag assignment.
+ */
+ public static TagAssignment noValue() {
+ return NO_VALUE;
+ }
+
+ /**
+ * Creates an assignment with values.
+ *
+ * @param values The assignment values.
+ * @return The tag assignment.
+ */
+ public static TagAssignment ofValues(String... values) {
+ Preconditions.checkArgument(
+ values != null && values.length > 0, "Assignment values must not be
null or empty");
+ for (String value : values) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(value), "Assignment values must not contain
blank values");
+ }
+
+ return new TagAssignment(values);
+ }
+
+ /**
+ * @return True if the assignment has one or more values.
+ */
+ public boolean hasValues() {
+ return values.length > 0;
+ }
+
+ /**
+ * @return The assignment values. Empty when this assignment has no value.
+ */
+ public String[] values() {
+ return values.clone();
+ }
+
+ /**
+ * Compares this assignment with another object.
+ *
+ * @param o The object to compare.
+ * @return True if the object is equal to this assignment.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TagAssignment)) {
+ return false;
+ }
+
+ TagAssignment that = (TagAssignment) o;
+ return Arrays.equals(values, that.values);
+ }
+
+ /**
+ * @return The hash code of this assignment.
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(values);
+ }
+
+ /**
+ * @return The string representation of this assignment.
+ */
+ @Override
+ public String toString() {
+ return "TagAssignment{" + "values=" + Arrays.toString(values) + "}";
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
b/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
index c58ceeda8e..ea9045d92b 100644
--- a/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
+++ b/api/src/main/java/org/apache/gravitino/tag/TagOperations.java
@@ -70,6 +70,31 @@ public interface TagOperations {
Tag createTag(String name, String comment, Map<String, String> properties)
throws TagAlreadyExistsException;
+ /**
+ * Create a tag under a metalake with an assignment value constraint.
+ *
+ * @param name The name of the tag.
+ * @param comment The comment of the tag.
+ * @param properties The properties of the tag.
+ * @param valueConstraint The assignment value constraint of the tag.
+ * @return The created tag.
+ * @throws TagAlreadyExistsException If the tag already exists.
+ * @throws UnsupportedOperationException If non-default value constraints
are not supported.
+ */
+ default Tag createTag(
+ String name,
+ String comment,
+ Map<String, String> properties,
+ TagValueConstraint valueConstraint)
+ throws TagAlreadyExistsException {
+ if (valueConstraint == null ||
TagValueConstraint.anyValue().equals(valueConstraint)) {
+ return createTag(name, comment, properties);
+ }
+
+ throw new UnsupportedOperationException(
+ "Creating tags with value constraints is not supported");
+ }
+
/**
* Alter a tag under a metalake.
*
diff --git a/api/src/main/java/org/apache/gravitino/tag/TagValue.java
b/api/src/main/java/org/apache/gravitino/tag/TagValue.java
new file mode 100644
index 0000000000..919aa81dd8
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/tag/TagValue.java
@@ -0,0 +1,114 @@
+/*
+ * 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.tag;
+
+import com.google.common.base.Preconditions;
+import java.util.Objects;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.annotation.Evolving;
+
+/** Represents one tag assignment value in tag association requests. */
+@Evolving
+public final class TagValue {
+
+ private final String name;
+
+ @Nullable private final String value;
+
+ private TagValue(String name, @Nullable String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * Creates a valued tag assignment value.
+ *
+ * @param name The tag name.
+ * @param value The assignment value.
+ * @return The tag value.
+ */
+ public static TagValue of(String name, String value) {
+ Preconditions.checkArgument(StringUtils.isNotBlank(name), "Tag name must
not be blank");
+ Preconditions.checkArgument(StringUtils.isNotBlank(value), "Tag value must
not be blank");
+ return new TagValue(name, value);
+ }
+
+ /**
+ * Creates a tag assignment with no value.
+ *
+ * @param name The tag name.
+ * @return The tag value.
+ */
+ public static TagValue noValue(String name) {
+ Preconditions.checkArgument(StringUtils.isNotBlank(name), "Tag name must
not be blank");
+ return new TagValue(name, null);
+ }
+
+ /**
+ * @return The tag name.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * @return The optional assignment value.
+ */
+ public Optional<String> value() {
+ return Optional.ofNullable(value);
+ }
+
+ /**
+ * Compares this value with another object.
+ *
+ * @param o The object to compare.
+ * @return True if the object is equal to this value.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TagValue)) {
+ return false;
+ }
+
+ TagValue that = (TagValue) o;
+ return Objects.equals(name, that.name) && Objects.equals(value,
that.value);
+ }
+
+ /**
+ * @return The hash code of this tag value.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, value);
+ }
+
+ /**
+ * @return The string representation of this tag value.
+ */
+ @Override
+ public String toString() {
+ return "TagValue{" + "name=" + name + ", value=" + value + "}";
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/tag/TagValueConstraint.java
b/api/src/main/java/org/apache/gravitino/tag/TagValueConstraint.java
new file mode 100644
index 0000000000..49f68040be
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/tag/TagValueConstraint.java
@@ -0,0 +1,158 @@
+/*
+ * 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.tag;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.annotation.Evolving;
+
+/** Describes what assignment values a tag accepts. */
+@Evolving
+public final class TagValueConstraint {
+
+ private static final TagValueConstraint ANY_VALUE =
+ new TagValueConstraint(Type.ANY_VALUE, new String[0]);
+
+ private static final TagValueConstraint NO_VALUE =
+ new TagValueConstraint(Type.NO_VALUE, new String[0]);
+
+ private final Type type;
+ private final String[] allowedValues;
+
+ private TagValueConstraint(Type type, String[] allowedValues) {
+ this.type = type;
+ this.allowedValues = allowedValues.clone();
+ }
+
+ /** The value constraint type. */
+ public enum Type {
+ /**
+ * The tag has no assignment value restriction and accepts assignments
with no value or with
+ * values.
+ */
+ ANY_VALUE,
+
+ /** The tag only accepts assignments with no value. */
+ NO_VALUE,
+
+ /** The tag only accepts values from the allowed value list. */
+ ALLOWED_VALUES
+ }
+
+ /**
+ * Creates a constraint with no assignment value restriction.
+ *
+ * <p>The tag may be assigned with no value or with any non-empty assignment
value.
+ *
+ * @return The value constraint.
+ */
+ public static TagValueConstraint anyValue() {
+ return ANY_VALUE;
+ }
+
+ /**
+ * Creates a constraint that only accepts assignments with no value.
+ *
+ * @return The value constraint.
+ */
+ public static TagValueConstraint noValue() {
+ return NO_VALUE;
+ }
+
+ /**
+ * Creates a constraint that only accepts values from the allowed value list.
+ *
+ * @param allowedValues The allowed assignment values.
+ * @return The value constraint.
+ */
+ public static TagValueConstraint ofAllowedValues(String... allowedValues) {
+ Preconditions.checkArgument(
+ allowedValues != null && allowedValues.length > 0,
+ "Allowed values must not be null or empty");
+ for (String allowedValue : allowedValues) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(allowedValue), "Allowed values must not
contain blank values");
+ }
+
+ return new TagValueConstraint(Type.ALLOWED_VALUES, allowedValues);
+ }
+
+ /**
+ * @return The value constraint type.
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * @return The allowed values. Empty when the type is not {@link
Type#ALLOWED_VALUES}.
+ */
+ public String[] allowedValues() {
+ return allowedValues.clone();
+ }
+
+ /**
+ * Compares this constraint with another object.
+ *
+ * @param o The object to compare.
+ * @return True if the object is equal to this constraint.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TagValueConstraint)) {
+ return false;
+ }
+
+ TagValueConstraint that = (TagValueConstraint) o;
+ return type == that.type && Arrays.equals(allowedValues,
that.allowedValues);
+ }
+
+ /**
+ * @return The hash code of this constraint.
+ */
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(type);
+ result = 31 * result + Arrays.hashCode(allowedValues);
+ return result;
+ }
+
+ /**
+ * @return The string representation of this constraint.
+ */
+ @Override
+ public String toString() {
+ if (type != Type.ALLOWED_VALUES) {
+ return "TagValueConstraint{" + "type=" + type + "}";
+ }
+
+ return "TagValueConstraint{"
+ + "type="
+ + type
+ + ", allowedValues="
+ + Arrays.toString(allowedValues)
+ + "}";
+ }
+}
diff --git a/api/src/test/java/org/apache/gravitino/tag/TestTagAssignment.java
b/api/src/test/java/org/apache/gravitino/tag/TestTagAssignment.java
new file mode 100644
index 0000000000..e977aa0412
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/tag/TestTagAssignment.java
@@ -0,0 +1,69 @@
+/*
+ * 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.tag;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestTagAssignment {
+
+ @Test
+ void testNoValueAssignment() {
+ TagAssignment assignment = TagAssignment.noValue();
+
+ Assertions.assertFalse(assignment.hasValues());
+ Assertions.assertArrayEquals(new String[0], assignment.values());
+ }
+
+ @Test
+ void testAssignmentWithValues() {
+ String[] values = new String[] {"finance", "risk"};
+ TagAssignment assignment = TagAssignment.ofValues(values);
+ values[0] = "changed";
+
+ Assertions.assertTrue(assignment.hasValues());
+ Assertions.assertArrayEquals(new String[] {"finance", "risk"},
assignment.values());
+
+ String[] returnedValues = assignment.values();
+ returnedValues[0] = "changed";
+ Assertions.assertArrayEquals(new String[] {"finance", "risk"},
assignment.values());
+ }
+
+ @Test
+ void testRejectInvalidValues() {
+ Assertions.assertThrows(IllegalArgumentException.class,
TagAssignment::ofValues);
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () ->
TagAssignment.ofValues("finance", null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () ->
TagAssignment.ofValues("finance", " "));
+ }
+
+ @Test
+ void testEqualsAndHashCode() {
+ TagAssignment assignment1 = TagAssignment.ofValues("finance", "risk");
+ TagAssignment assignment2 = TagAssignment.ofValues("finance", "risk");
+ TagAssignment assignment3 = TagAssignment.ofValues("risk", "finance");
+
+ Assertions.assertEquals(TagAssignment.noValue(), TagAssignment.noValue());
+ Assertions.assertEquals(assignment1, assignment2);
+ Assertions.assertEquals(assignment1.hashCode(), assignment2.hashCode());
+ Assertions.assertNotEquals(assignment1, assignment3);
+ }
+}
diff --git a/api/src/test/java/org/apache/gravitino/tag/TestTagValue.java
b/api/src/test/java/org/apache/gravitino/tag/TestTagValue.java
new file mode 100644
index 0000000000..d73920f1bf
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/tag/TestTagValue.java
@@ -0,0 +1,86 @@
+/*
+ * 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.tag;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestTagValue {
+
+ @Test
+ void testValuedTagValue() {
+ TagValue tagValue = TagValue.of("data_domain", "finance");
+
+ Assertions.assertEquals("data_domain", tagValue.name());
+ Assertions.assertEquals("finance", tagValue.value().get());
+ }
+
+ @Test
+ void testNoValueTagValue() {
+ TagValue tagValue = TagValue.noValue("pii");
+
+ Assertions.assertEquals("pii", tagValue.name());
+ Assertions.assertFalse(tagValue.value().isPresent());
+ }
+
+ @Test
+ void testConstructorIsPrivate() {
+ Constructor<?>[] constructors = TagValue.class.getDeclaredConstructors();
+
+ Assertions.assertEquals(1, constructors.length);
+ Assertions.assertTrue(Modifier.isPrivate(constructors[0].getModifiers()));
+ Assertions.assertEquals(0, TagValue.class.getConstructors().length);
+ }
+
+ @Test
+ void testNoJavaBeanAccessors() {
+ Assertions.assertThrows(NoSuchMethodException.class, () ->
TagValue.class.getMethod("getName"));
+ Assertions.assertThrows(
+ NoSuchMethodException.class, () -> TagValue.class.getMethod("setName",
String.class));
+ Assertions.assertThrows(
+ NoSuchMethodException.class, () ->
TagValue.class.getMethod("getValue"));
+ Assertions.assertThrows(
+ NoSuchMethodException.class, () ->
TagValue.class.getMethod("setValue", String.class));
+ }
+
+ @Test
+ void testRejectInvalidInput() {
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
TagValue.of(null, "finance"));
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
TagValue.of(" ", "finance"));
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
TagValue.of("tag", null));
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
TagValue.of("tag", " "));
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
TagValue.noValue(" "));
+ }
+
+ @Test
+ void testEqualsAndHashCode() {
+ TagValue tagValue1 = TagValue.of("data_domain", "finance");
+ TagValue tagValue2 = TagValue.of("data_domain", "finance");
+ TagValue tagValue3 = TagValue.of("data_domain", "risk");
+ TagValue tagValue4 = TagValue.noValue("data_domain");
+
+ Assertions.assertEquals(tagValue1, tagValue2);
+ Assertions.assertEquals(tagValue1.hashCode(), tagValue2.hashCode());
+ Assertions.assertNotEquals(tagValue1, tagValue3);
+ Assertions.assertNotEquals(tagValue1, tagValue4);
+ }
+}
diff --git
a/api/src/test/java/org/apache/gravitino/tag/TestTagValueConstraint.java
b/api/src/test/java/org/apache/gravitino/tag/TestTagValueConstraint.java
new file mode 100644
index 0000000000..826bf0a1de
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/tag/TestTagValueConstraint.java
@@ -0,0 +1,89 @@
+/*
+ * 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.tag;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestTagValueConstraint {
+
+ @Test
+ void testAnyValueConstraint() {
+ TagValueConstraint constraint = TagValueConstraint.anyValue();
+
+ Assertions.assertEquals(TagValueConstraint.Type.ANY_VALUE,
constraint.type());
+ Assertions.assertArrayEquals(new String[0], constraint.allowedValues());
+ }
+
+ @Test
+ void testNoValueConstraint() {
+ TagValueConstraint constraint = TagValueConstraint.noValue();
+
+ Assertions.assertEquals(TagValueConstraint.Type.NO_VALUE,
constraint.type());
+ Assertions.assertArrayEquals(new String[0], constraint.allowedValues());
+ }
+
+ @Test
+ void testAllowedValuesConstraint() {
+ String[] allowedValues = new String[] {"finance", "risk"};
+ TagValueConstraint constraint =
TagValueConstraint.ofAllowedValues(allowedValues);
+ allowedValues[0] = "changed";
+
+ Assertions.assertEquals(TagValueConstraint.Type.ALLOWED_VALUES,
constraint.type());
+ Assertions.assertArrayEquals(new String[] {"finance", "risk"},
constraint.allowedValues());
+
+ String[] returnedValues = constraint.allowedValues();
+ returnedValues[0] = "changed";
+ Assertions.assertArrayEquals(new String[] {"finance", "risk"},
constraint.allowedValues());
+ }
+
+ @Test
+ void testRejectInvalidAllowedValues() {
+ Assertions.assertThrows(IllegalArgumentException.class,
TagValueConstraint::ofAllowedValues);
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () ->
TagValueConstraint.ofAllowedValues("finance", null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () ->
TagValueConstraint.ofAllowedValues("finance", " "));
+ }
+
+ @Test
+ void testToStringDifferentiatesConstraintTypes() {
+ Assertions.assertEquals(
+ "TagValueConstraint{type=ANY_VALUE}",
TagValueConstraint.anyValue().toString());
+ Assertions.assertEquals(
+ "TagValueConstraint{type=NO_VALUE}",
TagValueConstraint.noValue().toString());
+ Assertions.assertEquals(
+ "TagValueConstraint{type=ALLOWED_VALUES, allowedValues=[finance,
risk]}",
+ TagValueConstraint.ofAllowedValues("finance", "risk").toString());
+ }
+
+ @Test
+ void testEqualsAndHashCode() {
+ TagValueConstraint constraint1 =
TagValueConstraint.ofAllowedValues("finance", "risk");
+ TagValueConstraint constraint2 =
TagValueConstraint.ofAllowedValues("finance", "risk");
+ TagValueConstraint constraint3 =
TagValueConstraint.ofAllowedValues("risk", "finance");
+
+ Assertions.assertEquals(TagValueConstraint.anyValue(),
TagValueConstraint.anyValue());
+ Assertions.assertEquals(TagValueConstraint.noValue(),
TagValueConstraint.noValue());
+ Assertions.assertEquals(constraint1, constraint2);
+ Assertions.assertEquals(constraint1.hashCode(), constraint2.hashCode());
+ Assertions.assertNotEquals(constraint1, constraint3);
+ }
+}