This is an automated email from the ASF dual-hosted git repository.

jshao 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 44d55effba [#6523] fix(core): Filter null prop when init properties 
(#6524)
44d55effba is described below

commit 44d55effba1782e73985bf1b4261c8017e508848
Author: Tianhang <58762426+teoteo...@users.noreply.github.com>
AuthorDate: Wed Mar 26 06:44:29 2025 +0800

    [#6523] fix(core): Filter null prop when init properties (#6524)
    
    ### What changes were proposed in this pull request?
    
    Filters out any entries with null keys/values before collection, to
    prevent potential NullPointerExceptions when accessing entries
    
    
    ### Why are the changes needed?
    
    Due to the cancellation of the operation to reload the topic to fill in
    its other properties after the alter operation in
    https://github.com/apache/gravitino/issues/3729, there is a risk that
    the value of the downstream property may return null, which in turn
    could trigger a NullPointerException in the properties() method of the
    Topic class.
    
    Fix: #6523
    
    ### Does this PR introduce _any_ user-facing change?
    
    no
    
    ### How was this patch tested?
    
    UT added
    
    ---------
    
    Co-authored-by: teo <litianh...@bilibili.co>
---
 .../gravitino/catalog/EntityCombinedFileset.java   |   1 +
 .../gravitino/catalog/EntityCombinedModel.java     |   1 +
 .../catalog/EntityCombinedModelVersion.java        |   1 +
 .../gravitino/catalog/EntityCombinedSchema.java    |   1 +
 .../gravitino/catalog/EntityCombinedTable.java     |   1 +
 .../gravitino/catalog/EntityCombinedTopic.java     |   1 +
 .../gravitino/meta/TestEntityCombinedObject.java   | 183 +++++++++++++++++++++
 7 files changed, 189 insertions(+)

diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedFileset.java 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedFileset.java
index c7b847fc9c..9b7fc8bd35 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedFileset.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedFileset.java
@@ -77,6 +77,7 @@ public final class EntityCombinedFileset implements Fileset {
   public Map<String, String> properties() {
     return fileset.properties().entrySet().stream()
         .filter(p -> !hiddenProperties.contains(p.getKey()))
+        .filter(entry -> entry.getKey() != null && entry.getValue() != null)
         .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModel.java 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModel.java
index 4aeefa0be5..740a5dbd5e 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModel.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModel.java
@@ -69,6 +69,7 @@ public final class EntityCombinedModel implements Model {
         ? null
         : model.properties().entrySet().stream()
             .filter(e -> !hiddenProperties.contains(e.getKey()))
+            .filter(entry -> entry.getKey() != null && entry.getValue() != 
null)
             .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModelVersion.java
 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModelVersion.java
index b41e2889de..748a5c1238 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModelVersion.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedModelVersion.java
@@ -71,6 +71,7 @@ public final class EntityCombinedModelVersion implements 
ModelVersion {
         ? null
         : modelVersion.properties().entrySet().stream()
             .filter(e -> !hiddenProperties.contains(e.getKey()))
+            .filter(entry -> entry.getKey() != null && entry.getValue() != 
null)
             .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedSchema.java 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedSchema.java
index ce3d0a3be7..a9cd7cfe8a 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedSchema.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedSchema.java
@@ -85,6 +85,7 @@ public final class EntityCombinedSchema implements Schema {
   public Map<String, String> properties() {
     return schema.properties().entrySet().stream()
         .filter(e -> !hiddenProperties.contains(e.getKey()))
+        .filter(entry -> entry.getKey() != null && entry.getValue() != null)
         .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTable.java 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTable.java
index 70cbd0ace4..a70699ac7f 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTable.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTable.java
@@ -96,6 +96,7 @@ public final class EntityCombinedTable implements Table {
   public Map<String, String> properties() {
     return table.properties().entrySet().stream()
         .filter(p -> !hiddenProperties.contains(p.getKey()))
+        .filter(entry -> entry.getKey() != null && entry.getValue() != null)
         .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTopic.java 
b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTopic.java
index 972df622b3..6193fadacf 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTopic.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/EntityCombinedTopic.java
@@ -84,6 +84,7 @@ public class EntityCombinedTopic implements Topic {
   public Map<String, String> properties() {
     return topic.properties().entrySet().stream()
         .filter(p -> !hiddenProperties.contains(p.getKey()))
+        .filter(entry -> entry.getKey() != null && entry.getValue() != null)
         .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
   }
 
diff --git 
a/core/src/test/java/org/apache/gravitino/meta/TestEntityCombinedObject.java 
b/core/src/test/java/org/apache/gravitino/meta/TestEntityCombinedObject.java
new file mode 100644
index 0000000000..a24b810bf7
--- /dev/null
+++ b/core/src/test/java/org/apache/gravitino/meta/TestEntityCombinedObject.java
@@ -0,0 +1,183 @@
+/*
+ * 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.meta;
+
+import static org.mockito.Mockito.mock;
+
+import com.google.common.collect.ImmutableSet;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.catalog.EntityCombinedFileset;
+import org.apache.gravitino.catalog.EntityCombinedModel;
+import org.apache.gravitino.catalog.EntityCombinedSchema;
+import org.apache.gravitino.catalog.EntityCombinedTable;
+import org.apache.gravitino.catalog.EntityCombinedTopic;
+import org.apache.gravitino.file.Fileset;
+import org.apache.gravitino.messaging.Topic;
+import org.apache.gravitino.model.Model;
+import org.apache.gravitino.rel.Table;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestEntityCombinedObject {
+  private final AuditInfo auditInfo =
+      AuditInfo.builder()
+          .withCreator("creator")
+          .withCreateTime(Instant.parse("2025-01-01T00:00:00Z"))
+          .withLastModifier("modifier")
+          .withLastModifiedTime(Instant.parse("2025-01-02T00:00:00Z"))
+          .build();
+
+  // Properties test data;
+  private final Map<String, String> entityProperties =
+      new HashMap<String, String>() {
+        {
+          put("k1", "v1");
+          put("k2", "v2");
+          put("k3", "k3");
+          put("k5", null);
+          put(null, null);
+        }
+      };
+  private final Set<String> hiddenProperties = ImmutableSet.of("k3", "k4");
+  private final Schema originSchema = mockSchema();
+  private final Topic originTopic = mockTopic();
+  private final Table originTable = mockTable();
+  private final Fileset originFileset = mockFileset();
+  private final Model originModel = mockModel();
+
+  @Test
+  public void testSchema() {
+    EntityCombinedSchema entityCombinedSchema =
+        
EntityCombinedSchema.of(originSchema).withHiddenProperties(hiddenProperties);
+    Assertions.assertEquals(originSchema.name(), entityCombinedSchema.name());
+    Assertions.assertEquals(originSchema.comment(), 
entityCombinedSchema.comment());
+    Map<String, String> filterProp = new HashMap<>(originSchema.properties());
+    filterProp.remove("k3");
+    filterProp.remove(null);
+    filterProp.remove("k5");
+    Assertions.assertEquals(filterProp, entityCombinedSchema.properties());
+    Assertions.assertEquals(originSchema.auditInfo(), 
entityCombinedSchema.auditInfo());
+  }
+
+  @Test
+  public void testTopic() {
+    EntityCombinedTopic entityCombinedTopic =
+        
EntityCombinedTopic.of(originTopic).withHiddenProperties(hiddenProperties);
+    Assertions.assertEquals(originTopic.name(), entityCombinedTopic.name());
+    Assertions.assertEquals(originTopic.comment(), 
entityCombinedTopic.comment());
+    Map<String, String> filterProp = new HashMap<>(originTopic.properties());
+    filterProp.remove("k3");
+    filterProp.remove(null);
+    filterProp.remove("k5");
+    Assertions.assertEquals(filterProp, entityCombinedTopic.properties());
+    Assertions.assertEquals(originTopic.auditInfo(), 
entityCombinedTopic.auditInfo());
+  }
+
+  @Test
+  public void testTable() {
+    EntityCombinedTable entityCombinedTable =
+        
EntityCombinedTable.of(originTable).withHiddenProperties(hiddenProperties);
+    Assertions.assertEquals(originTable.name(), entityCombinedTable.name());
+    Assertions.assertEquals(originTable.comment(), 
entityCombinedTable.comment());
+    Map<String, String> filterProp = new HashMap<>(originTopic.properties());
+    filterProp.remove("k3");
+    filterProp.remove(null);
+    filterProp.remove("k5");
+    Assertions.assertEquals(filterProp, entityCombinedTable.properties());
+    Assertions.assertEquals(originTable.auditInfo(), 
entityCombinedTable.auditInfo());
+  }
+
+  @Test
+  public void testFileset() {
+    EntityCombinedFileset entityCombinedFileset =
+        
EntityCombinedFileset.of(originFileset).withHiddenProperties(hiddenProperties);
+    Assertions.assertEquals(originFileset.name(), 
entityCombinedFileset.name());
+    Assertions.assertEquals(originFileset.comment(), 
entityCombinedFileset.comment());
+    Map<String, String> filterProp = new HashMap<>(originTopic.properties());
+    filterProp.remove("k3");
+    filterProp.remove(null);
+    filterProp.remove("k5");
+    Assertions.assertEquals(filterProp, entityCombinedFileset.properties());
+    Assertions.assertEquals(originFileset.auditInfo(), 
entityCombinedFileset.auditInfo());
+  }
+
+  @Test
+  public void testModel() {
+    EntityCombinedModel entityCombinedModel =
+        
EntityCombinedModel.of(originModel).withHiddenProperties(hiddenProperties);
+    Assertions.assertEquals(originModel.name(), entityCombinedModel.name());
+    Assertions.assertEquals(originModel.comment(), 
entityCombinedModel.comment());
+    Map<String, String> filterProp = new HashMap<>(originModel.properties());
+    filterProp.remove("k3");
+    filterProp.remove(null);
+    filterProp.remove("k5");
+    Assertions.assertEquals(filterProp, entityCombinedModel.properties());
+    Assertions.assertEquals(originModel.auditInfo(), 
entityCombinedModel.auditInfo());
+  }
+
+  private Schema mockSchema() {
+    Schema mockSchema = mock(Schema.class);
+    Mockito.when(mockSchema.name()).thenReturn("testSchema");
+    Mockito.when(mockSchema.comment()).thenReturn("test schema comment");
+    Mockito.when(mockSchema.auditInfo()).thenReturn(auditInfo);
+    Mockito.when(mockSchema.properties()).thenReturn(entityProperties);
+    return mockSchema;
+  }
+
+  private Topic mockTopic() {
+    Topic mockTopic = mock(Topic.class);
+    Mockito.when(mockTopic.name()).thenReturn("testTopic");
+    Mockito.when(mockTopic.comment()).thenReturn("test topic comment");
+    Mockito.when(mockTopic.auditInfo()).thenReturn(auditInfo);
+    Mockito.when(mockTopic.properties()).thenReturn(entityProperties);
+    return mockTopic;
+  }
+
+  private Fileset mockFileset() {
+    Fileset mockFileset = mock(Fileset.class);
+    Mockito.when(mockFileset.name()).thenReturn("testFileset");
+    Mockito.when(mockFileset.comment()).thenReturn("test fileset comment");
+    Mockito.when(mockFileset.auditInfo()).thenReturn(auditInfo);
+    Mockito.when(mockFileset.properties()).thenReturn(entityProperties);
+    return mockFileset;
+  }
+
+  private Table mockTable() {
+    Table mockTable = mock(Table.class);
+    Mockito.when(mockTable.name()).thenReturn("testTable");
+    Mockito.when(mockTable.comment()).thenReturn("test table comment");
+    Mockito.when(mockTable.auditInfo()).thenReturn(auditInfo);
+    Mockito.when(mockTable.properties()).thenReturn(entityProperties);
+    return mockTable;
+  }
+
+  private Model mockModel() {
+    Model mockModel = mock(Model.class);
+    Mockito.when(mockModel.name()).thenReturn("testModel");
+    Mockito.when(mockModel.comment()).thenReturn("test model comment");
+    Mockito.when(mockModel.auditInfo()).thenReturn(auditInfo);
+    Mockito.when(mockModel.properties()).thenReturn(entityProperties);
+    return mockModel;
+  }
+}

Reply via email to