This is an automated email from the ASF dual-hosted git repository.
mchades 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 438da702f1 [#12009] improvement(core): Support custom case sensitivity
normalization in Capability (#12010)
438da702f1 is described below
commit 438da702f194cf441a421b354073ac8eb843d688
Author: Yuhui <[email protected]>
AuthorDate: Wed Jul 15 11:35:40 2026 +0800
[#12009] improvement(core): Support custom case sensitivity normalization
in Capability (#12010)
### What type of PR is it?
Improvement
### What does this PR do?
Adds a `default String normalizeName(Scope scope, String name)` method
to `Capability`, and updates `CapabilityHelpers` to call it instead of
hardcoding a lowercase fold. This lets catalogs override normalization
for case sensitivity behavior that isn't a simple lowercase fold (e.g.
uppercase-by-default catalogs, or ones supporting both quoted
case-sensitive and unquoted case-insensitive forms).
### Related issues
#12009
### Does this PR introduce any user-facing change?
No
### How was this patch tested?
`./gradlew :core:test --tests
"org.apache.gravitino.connector.capability.TestCapability"`
---
.../gravitino/catalog/CapabilityHelpers.java | 28 +++++--
.../gravitino/connector/capability/Capability.java | 20 +++++
.../gravitino/catalog/TestCapabilityHelpers.java | 95 ++++++++++++++++++++++
.../connector/capability/TestCapability.java | 8 ++
4 files changed, 142 insertions(+), 9 deletions(-)
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
b/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
index 5825a97142..d6d0a90e7e 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
@@ -173,9 +173,7 @@ public class CapabilityHelpers {
public static Partition applyCaseSensitive(Partition partition, Capability
capabilities) {
String newName =
-
capabilities.caseSensitiveOnName(Capability.Scope.PARTITION).supported()
- ? partition.name()
- : partition.name().toLowerCase();
+ applyCaseSensitiveOnName(Capability.Scope.PARTITION, partition.name(),
capabilities);
if (partition instanceof IdentityPartition) {
IdentityPartition identityPartition = (IdentityPartition) partition;
return Partitions.identity(
@@ -507,16 +505,28 @@ public class CapabilityHelpers {
public static String applyCaseSensitiveOnName(
Capability.Scope scope, String name, Capability capabilities) {
- return capabilities.caseSensitiveOnName(scope).supported() ? name :
name.toLowerCase();
+ if (name == null) {
+ return null;
+ }
+ String normalizedName = capabilities.normalizeName(scope, name);
+ if (normalizedName == null) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Capability.normalizeName(%s, %s) must not return null for a
non-null name",
+ scope, name));
+ }
+ return normalizedName;
}
private static String[] applyCaseSensitiveOnColumnName(String[] name,
Capability capabilities) {
- if
(!capabilities.caseSensitiveOnName(Capability.Scope.COLUMN).supported()) {
- String[] standardizeColumnName = Arrays.copyOf(name, name.length);
- standardizeColumnName[0] = name[0].toLowerCase();
- return standardizeColumnName;
+ String normalizedFirstName =
+ applyCaseSensitiveOnName(Capability.Scope.COLUMN, name[0],
capabilities);
+ if (normalizedFirstName.equals(name[0])) {
+ return name;
}
- return name;
+ String[] standardizeColumnName = Arrays.copyOf(name, name.length);
+ standardizeColumnName[0] = normalizedFirstName;
+ return standardizeColumnName;
}
private static void applyColumnNotNull(Column column, Capability
capabilities) {
diff --git
a/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
b/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
index f787a16d4f..66a6e11d61 100644
---
a/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
+++
b/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
@@ -19,6 +19,7 @@
package org.apache.gravitino.connector.capability;
import com.google.common.collect.ImmutableSet;
+import java.util.Locale;
import java.util.Set;
import org.apache.gravitino.MetadataObjects;
import org.apache.gravitino.annotation.Evolving;
@@ -73,6 +74,25 @@ public interface Capability {
return DEFAULT.caseSensitiveOnName(scope);
}
+ /**
+ * Normalizes the given name to the catalog's canonical form before it is
used to call the
+ * underlying catalog operation and persisted as the Gravitino entity name.
This is invoked
+ * instead of the simple lowercase folding implied by {@link
#caseSensitiveOnName(Scope)} whenever
+ * a catalog needs custom folding logic (for example, a catalog whose native
folding direction is
+ * uppercase, or one that supports a case-sensitive quoted form alongside a
case-insensitive
+ * unquoted form).
+ *
+ * @param scope The scope of the capability.
+ * @param name The name to normalize.
+ * @return The normalized, canonical name.
+ */
+ default String normalizeName(Scope scope, String name) {
+ if (name == null) {
+ return null;
+ }
+ return caseSensitiveOnName(scope).supported() ? name :
name.toLowerCase(Locale.ROOT);
+ }
+
/**
* Check if the name is illegal in the scope, such as special characters,
reserved words, etc.
*
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestCapabilityHelpers.java
b/core/src/test/java/org/apache/gravitino/catalog/TestCapabilityHelpers.java
new file mode 100644
index 0000000000..02c0c051fd
--- /dev/null
+++ b/core/src/test/java/org/apache/gravitino/catalog/TestCapabilityHelpers.java
@@ -0,0 +1,95 @@
+/*
+ * 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.catalog;
+
+import java.util.Locale;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.connector.capability.CapabilityResult;
+import org.apache.gravitino.rel.expressions.literals.Literal;
+import org.apache.gravitino.rel.expressions.literals.Literals;
+import org.apache.gravitino.rel.partitions.IdentityPartition;
+import org.apache.gravitino.rel.partitions.Partition;
+import org.apache.gravitino.rel.partitions.Partitions;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestCapabilityHelpers {
+
+ private static final Capability UPPERCASE_CAPABILITY =
+ new Capability() {
+ @Override
+ public CapabilityResult caseSensitiveOnName(Scope scope) {
+ return CapabilityResult.unsupported("not case sensitive");
+ }
+
+ @Override
+ public String normalizeName(Scope scope, String name) {
+ return name.toUpperCase(Locale.ROOT);
+ }
+ };
+
+ private static final Capability NULL_NORMALIZING_CAPABILITY =
+ new Capability() {
+ @Override
+ public String normalizeName(Scope scope, String name) {
+ return null;
+ }
+ };
+
+ @Test
+ void testApplyCaseSensitiveOnNameHonorsCustomNormalizeName() {
+ String normalized =
+ CapabilityHelpers.applyCaseSensitiveOnName(
+ Capability.Scope.TABLE, "myTable", UPPERCASE_CAPABILITY);
+ Assertions.assertEquals("MYTABLE", normalized);
+ }
+
+ @Test
+ void testApplyCaseSensitivePartitionHonorsCustomNormalizeName() {
+ IdentityPartition partition =
+ Partitions.identity(
+ "myPartition",
+ new String[][] {{"col1"}},
+ new Literal<?>[] {Literals.stringLiteral("val1")},
+ null);
+
+ Partition result = CapabilityHelpers.applyCaseSensitive(partition,
UPPERCASE_CAPABILITY);
+ Assertions.assertEquals("MYPARTITION", result.name());
+ }
+
+ @Test
+ void testApplyCaseSensitiveOnNameRejectsNullNormalizeNameResult() {
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ CapabilityHelpers.applyCaseSensitiveOnName(
+ Capability.Scope.TABLE, "myTable",
NULL_NORMALIZING_CAPABILITY));
+ }
+
+ @Test
+ void testApplyCaseSensitiveOnNameAllowsNullNameToPassThrough() {
+ // A null name (e.g. an auto-generated identity partition name yet to be
assigned) must be
+ // allowed to pass through as null rather than being rejected as an
invalid normalizeName
+ // result.
+ String normalized =
+ CapabilityHelpers.applyCaseSensitiveOnName(
+ Capability.Scope.PARTITION, null, Capability.DEFAULT);
+ Assertions.assertNull(normalized);
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/connector/capability/TestCapability.java
b/core/src/test/java/org/apache/gravitino/connector/capability/TestCapability.java
index 2a4390cc61..e5e4893aab 100644
---
a/core/src/test/java/org/apache/gravitino/connector/capability/TestCapability.java
+++
b/core/src/test/java/org/apache/gravitino/connector/capability/TestCapability.java
@@ -94,4 +94,12 @@ public class TestCapability {
Assertions.assertTrue(result.unsupportedMessage().contains("is
illegal"));
}
}
+
+ @Test
+ void testDefaultNormalizeNameMatchesCaseSensitiveOnName() {
+ for (Capability.Scope scope : Capability.Scope.values()) {
+ // The default capability is case-sensitive, so normalizeName must be a
no-op.
+ Assertions.assertEquals("MixedCase",
Capability.DEFAULT.normalizeName(scope, "MixedCase"));
+ }
+ }
}