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 21b9c6e716 [#13317] fix(core): Validate view defaults and metalake 
creator name length before creating (#14636)
21b9c6e716 is described below

commit 21b9c6e716f087f27cbf3eed8ebb8bfb31ed7df3
Author: Roshan Banisetti <[email protected]>
AuthorDate: Thu Nov 5 01:58:55 2026 -0600

    [#13317] fix(core): Validate view defaults and metalake creator name length 
before creating (#14636)
    
    ### What changes were proposed in this pull request?
    
    Items 3 and 6 of #13317:
    
    - `ViewNormalizeDispatcher` checks `defaultCatalog` and `defaultSchema`
    against `EntityFieldLimits.MAX_NAME_LENGTH` on `createView`, and on
    `alterView` for a `ReplaceView` change, before the underlying catalog is
    called. This follows the comment check `TopicNormalizeDispatcher` does
    on create and alter.
    - `MetalakeHookDispatcher.createMetalake` checks the current user name
    against the same limit before the metalake is created, when access
    control is enabled (the only case where the creator is added as a user).
    
    ### Why are the changes needed?
    
    `view_version_info.default_catalog` / `default_schema` and
    `user_meta.user_name` are `VARCHAR(128)` in the H2, MySQL and PostgreSQL
    schemas.
    
    - A longer view default was accepted by the external catalog, and the
    entity store write that then failed was only logged, so the view existed
    without its Gravitino entity.
    - A creator name longer than 128 characters failed in `addUser` after
    the metalake was already committed, leaving the metalake without its
    creator user and owner.
    
    Both now fail with an `IllegalArgumentException` before anything is
    created, with the message format from #13312 (for example "The default
    catalog of the view must not exceed 128 characters").
    
    Part of #13317. Item 2 needs no change, per the discussion on the issue.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Creating or replacing a view with a default catalog or schema longer
    than 128 characters, and creating a metalake as a user whose name is
    longer than 128 characters (with access control enabled), are now
    rejected up front instead of partly succeeding.
    
    ### How was this patch tested?
    
    - `TestViewNormalizeDispatcher.testDefaultsLength`: a 129-character
    default catalog or schema is rejected on create (and the view is not
    created) and on `ReplaceView`; 128 characters are accepted and the
    stored view is unchanged after the rejected alter.
    -
    `TestMetalakeHookDispatcher.testCreateMetalakeRejectsTooLongCreatorName`:
    a 129-character creator name is rejected without calling
    `createMetalake` or `addUser`; a 128-character name creates the metalake
    and adds the user.
    - `./gradlew :core:test -PskipITs`: 3341 tests, 0 failures (2 skipped).
    `:core:spotlessCheck` passes.
    - With the two main-code changes reverted, both new tests fail.
---
 .../gravitino/catalog/ViewNormalizeDispatcher.java | 21 ++++++++
 .../gravitino/hook/MetalakeHookDispatcher.java     | 15 +++++-
 .../catalog/TestViewNormalizeDispatcher.java       | 63 ++++++++++++++++++++++
 .../gravitino/hook/TestMetalakeHookDispatcher.java | 30 +++++++++++
 4 files changed, 127 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/ViewNormalizeDispatcher.java 
b/core/src/main/java/org/apache/gravitino/catalog/ViewNormalizeDispatcher.java
index 5c786a1ce1..9ab5f85b1d 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/ViewNormalizeDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/ViewNormalizeDispatcher.java
@@ -24,6 +24,8 @@ import static 
org.apache.gravitino.catalog.CapabilityHelpers.getCapability;
 
 import java.util.Map;
 import javax.annotation.Nullable;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.EntityFieldLimits;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.connector.capability.Capability;
@@ -77,6 +79,8 @@ public class ViewNormalizeDispatcher implements 
ViewDispatcher {
       @Nullable String defaultSchema,
       Map<String, String> properties)
       throws NoSuchSchemaException, ViewAlreadyExistsException {
+    // Check the defaults before the underlying catalog creates the view.
+    checkDefaultsLength(defaultCatalog, defaultSchema);
     return dispatcher.createView(
         normalizeNameIdentifier(ident),
         comment,
@@ -90,6 +94,12 @@ public class ViewNormalizeDispatcher implements 
ViewDispatcher {
   @Override
   public View alterView(NameIdentifier ident, ViewChange... changes)
       throws NoSuchViewException, IllegalArgumentException {
+    for (ViewChange change : changes) {
+      if (change instanceof ViewChange.ReplaceView) {
+        ViewChange.ReplaceView replaceView = (ViewChange.ReplaceView) change;
+        checkDefaultsLength(replaceView.getDefaultCatalog(), 
replaceView.getDefaultSchema());
+      }
+    }
     Capability capability = getCapability(ident, catalogManager);
     return dispatcher.alterView(
         normalizeCaseSensitive(ident), applyCapabilities(capability, changes));
@@ -110,6 +120,17 @@ public class ViewNormalizeDispatcher implements 
ViewDispatcher {
     return applyCaseSensitive(ident, Capability.Scope.VIEW, capabilities);
   }
 
+  private static void checkDefaultsLength(
+      @Nullable String defaultCatalog, @Nullable String defaultSchema) {
+    EntityFieldLimits.checkMaxLength(
+        defaultCatalog,
+        EntityFieldLimits.MAX_NAME_LENGTH,
+        "default catalog",
+        Entity.EntityType.VIEW);
+    EntityFieldLimits.checkMaxLength(
+        defaultSchema, EntityFieldLimits.MAX_NAME_LENGTH, "default schema", 
Entity.EntityType.VIEW);
+  }
+
   private NameIdentifier normalizeNameIdentifier(NameIdentifier ident) {
     Capability capability = getCapability(ident, catalogManager);
     return applyCapabilities(ident, Capability.Scope.VIEW, capability);
diff --git 
a/core/src/main/java/org/apache/gravitino/hook/MetalakeHookDispatcher.java 
b/core/src/main/java/org/apache/gravitino/hook/MetalakeHookDispatcher.java
index c36350b92e..dfdaf653d9 100644
--- a/core/src/main/java/org/apache/gravitino/hook/MetalakeHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/MetalakeHookDispatcher.java
@@ -20,6 +20,7 @@ package org.apache.gravitino.hook;
 
 import java.util.Map;
 import org.apache.gravitino.Entity;
+import org.apache.gravitino.EntityFieldLimits;
 import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.Metalake;
 import org.apache.gravitino.MetalakeChange;
@@ -62,11 +63,21 @@ public class MetalakeHookDispatcher implements 
MetalakeDispatcher {
   public Metalake createMetalake(
       NameIdentifier ident, String comment, Map<String, String> properties)
       throws MetalakeAlreadyExistsException {
+    AccessControlDispatcher accessControlDispatcher =
+        GravitinoEnv.getInstance().internalAccessControlDispatcher();
+    if (accessControlDispatcher != null) {
+      // The creator is added as a user only after the metalake is committed, 
so check the name
+      // fits the user name column first.
+      EntityFieldLimits.checkMaxLength(
+          PrincipalUtils.getCurrentUserName(),
+          EntityFieldLimits.MAX_NAME_LENGTH,
+          "name",
+          Entity.EntityType.USER);
+    }
+
     Metalake metalake = dispatcher.createMetalake(ident, comment, properties);
 
     // Add the creator to the metalake.
-    AccessControlDispatcher accessControlDispatcher =
-        GravitinoEnv.getInstance().internalAccessControlDispatcher();
     if (accessControlDispatcher != null) {
       accessControlDispatcher.addUser(ident.name(), 
PrincipalUtils.getCurrentUserName());
     }
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestViewNormalizeDispatcher.java
 
b/core/src/test/java/org/apache/gravitino/catalog/TestViewNormalizeDispatcher.java
index b04015959d..4bb8825aaf 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestViewNormalizeDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestViewNormalizeDispatcher.java
@@ -22,6 +22,8 @@ import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.EntityFieldLimits;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.rel.Column;
@@ -103,4 +105,65 @@ public class TestViewNormalizeDispatcher extends 
TestOperationDispatcher {
     Assertions.assertEquals(
         "The VIEW name 'a?' is illegal. Illegal name: a?", 
exception.getMessage());
   }
+
+  @Test
+  public void testDefaultsLength() {
+    Namespace viewNs = Namespace.of(metalake, catalog, 
"schema_view_defaults_length");
+    Map<String, String> props = ImmutableMap.of("k1", "v1");
+    schemaNormalizeDispatcher.createSchema(NameIdentifier.of(viewNs.levels()), 
"comment", props);
+
+    NameIdentifier viewIdent = NameIdentifier.of(viewNs, "view");
+    Representation[] representations = {
+      SQLRepresentation.builder().withDialect("spark").withSql("SELECT 
1").build()
+    };
+    String maxLength = StringUtils.repeat("a", 
EntityFieldLimits.MAX_NAME_LENGTH);
+    String tooLong = maxLength + "a";
+
+    Exception exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                viewNormalizeDispatcher.createView(
+                    viewIdent, "comment", new Column[0], representations, 
tooLong, null, props));
+    Assertions.assertEquals(
+        "The default catalog of the view must not exceed 128 characters", 
exception.getMessage());
+    exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                viewNormalizeDispatcher.createView(
+                    viewIdent, "comment", new Column[0], representations, 
null, tooLong, props));
+    Assertions.assertEquals(
+        "The default schema of the view must not exceed 128 characters", 
exception.getMessage());
+    Assertions.assertFalse(viewNormalizeDispatcher.viewExists(viewIdent));
+
+    View view =
+        viewNormalizeDispatcher.createView(
+            viewIdent, "comment", new Column[0], representations, maxLength, 
maxLength, props);
+    Assertions.assertEquals(maxLength, view.defaultCatalog());
+    Assertions.assertEquals(maxLength, view.defaultSchema());
+
+    exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                viewNormalizeDispatcher.alterView(
+                    viewIdent,
+                    ViewChange.replaceView(
+                        new Column[0], representations, tooLong, null, 
"comment")));
+    Assertions.assertEquals(
+        "The default catalog of the view must not exceed 128 characters", 
exception.getMessage());
+    exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                viewNormalizeDispatcher.alterView(
+                    viewIdent,
+                    ViewChange.replaceView(
+                        new Column[0], representations, null, tooLong, 
"comment")));
+    Assertions.assertEquals(
+        "The default schema of the view must not exceed 128 characters", 
exception.getMessage());
+    Assertions.assertEquals(
+        maxLength, 
viewNormalizeDispatcher.loadView(viewIdent).defaultCatalog());
+  }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/hook/TestMetalakeHookDispatcher.java 
b/core/src/test/java/org/apache/gravitino/hook/TestMetalakeHookDispatcher.java
index 33f6ff18f5..814967c234 100644
--- 
a/core/src/test/java/org/apache/gravitino/hook/TestMetalakeHookDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/hook/TestMetalakeHookDispatcher.java
@@ -26,14 +26,18 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.util.Collections;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.EntityFieldLimits;
 import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.Metalake;
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.UserPrincipal;
 import org.apache.gravitino.authorization.AccessControlDispatcher;
 import org.apache.gravitino.authorization.OwnerDispatcher;
 import org.apache.gravitino.exceptions.UserAlreadyExistsException;
 import org.apache.gravitino.metalake.MetalakeDispatcher;
+import org.apache.gravitino.utils.PrincipalUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -137,4 +141,30 @@ public class TestMetalakeHookDispatcher {
     verify(mockAccessControlDispatcher).addUser(any(), any());
     verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
   }
+
+  @Test
+  public void testCreateMetalakeRejectsTooLongCreatorName() throws Exception {
+    NameIdentifier ident = NameIdentifier.of("test_metalake");
+    String tooLongName = StringUtils.repeat("a", 
EntityFieldLimits.MAX_NAME_LENGTH + 1);
+
+    IllegalArgumentException thrown =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                PrincipalUtils.doAs(
+                    new UserPrincipal(tooLongName),
+                    () -> hookDispatcher.createMetalake(ident, "comment", 
Collections.emptyMap())));
+    Assertions.assertEquals(
+        "The name of the user must not exceed 128 characters", 
thrown.getMessage());
+    // The metalake must not be created when its creator cannot be added as a 
user.
+    verify(mockDispatcher, never()).createMetalake(any(), any(), any());
+    verify(mockAccessControlDispatcher, never()).addUser(any(), any());
+
+    String maxLengthName = StringUtils.repeat("a", 
EntityFieldLimits.MAX_NAME_LENGTH);
+    PrincipalUtils.doAs(
+        new UserPrincipal(maxLengthName),
+        () -> hookDispatcher.createMetalake(ident, "comment", 
Collections.emptyMap()));
+    verify(mockDispatcher).createMetalake(any(), any(), any());
+    verify(mockAccessControlDispatcher).addUser("test_metalake", 
maxLengthName);
+  }
 }

Reply via email to