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

jshao pushed a commit to branch branch-1.0
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.0 by this push:
     new 031d7ec2d3 [#8408] improvement(sql): avoid returning [""] when no 
roles exist in GroupMetaH2Provider (#8428)
031d7ec2d3 is described below

commit 031d7ec2d3e61cf321cdb58fd9c4143a3866d11d
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Sep 4 15:49:29 2025 +0800

    [#8408] improvement(sql): avoid returning [""] when no roles exist in 
GroupMetaH2Provider (#8428)
    
    ### What changes were proposed in this pull request?
    In `GroupMetaH2Provider.listExtendedGroupPOsByMetalakeId`, both
    `roleNames` and `roleIds` now use:
    
    - Added a `CASE` expression inside `GROUP_CONCAT` to filter out `NULL`
    and empty strings, ensuring only valid role values are included i.e. not
    mapped to `""`
    - Used `COALESCE(GROUP_CONCAT(...), '')` to guarantee the aggregate
    never evaluates to `NULL`
    - Prevented the previous behavior where no roles resulted in `[""]`, by
    ensuring that when no valid entries exist the result becomes `[]`.
    - Preserved correct quoting of non-empty values (`"value"`) for
    compatibility with JSON parsing expectations.
    
    This guarantees clean JSON-like array strings.
    
    ### Why are the changes needed?
    
    Previously, if no roles existed, the query returned `[""]`, which
    misrepresents the absence of roles.
    With this fix, such cases now correctly return `[]`.
    This improves correctness and avoids downstream parsing issues.
    
    Fix: #8408
    
    ### Does this PR introduce _any_ user-facing change?
    Yes.
    - **Before:** empty roles → `[""]`
    - **After:** empty roles → `[]`
    - Non-empty cases remain unchanged (e.g. `["Admin","User"]`).
    - Invalid role values skipped (e.g. `["Admin","", null]` -->
    `["Admin"]`)
    
    ### How was this patch tested?
    - Verified with H2:
      - Groups with multiple roles → quoted list.
      - Groups with only empty/NULL roles → values skipped.
      - Groups with no roles → `[]`.
    - Added integration tests to confirm all scenarios.
    
    Co-authored-by: Khawaja Abdullah Ansar <[email protected]>
---
 .../mapper/provider/h2/GroupMetaH2Provider.java    |  16 ++-
 .../provider/h2/TestGroupMetaH2Provider.java       | 144 +++++++++++++++++++++
 2 files changed, 158 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/GroupMetaH2Provider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/GroupMetaH2Provider.java
index e975131e09..45de807267 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/GroupMetaH2Provider.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/GroupMetaH2Provider.java
@@ -33,8 +33,20 @@ public class GroupMetaH2Provider extends 
GroupMetaBaseSQLProvider {
         + " gt.audit_info as auditInfo,"
         + " gt.current_version as currentVersion, gt.last_version as 
lastVersion,"
         + " gt.deleted_at as deletedAt,"
-        + " '[' || GROUP_CONCAT('\"' || rot.role_name || '\"') || ']' as 
roleNames,"
-        + " '[' || GROUP_CONCAT('\"' || rot.role_id || '\"') || ']' as roleIds"
+        + " '[' || COALESCE(GROUP_CONCAT( "
+        + "        CASE "
+        + "          WHEN rot.role_name IS NOT NULL AND rot.role_name <> '' "
+        + "          THEN '\"' || rot.role_name || '\"' "
+        + "          ELSE NULL "
+        + "        END "
+        + "      ), '') || ']' as roleNames, "
+        + " '[' || COALESCE(GROUP_CONCAT( "
+        + "        CASE "
+        + "          WHEN rot.role_id IS NOT NULL "
+        + "          THEN '\"' || rot.role_id || '\"' "
+        + "          ELSE NULL "
+        + "        END "
+        + "      ), '') || ']' as roleIds "
         + " FROM "
         + GROUP_TABLE_NAME
         + " gt LEFT OUTER JOIN ("
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/mapper/provider/h2/TestGroupMetaH2Provider.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/mapper/provider/h2/TestGroupMetaH2Provider.java
new file mode 100644
index 0000000000..3d68c7f329
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/mapper/provider/h2/TestGroupMetaH2Provider.java
@@ -0,0 +1,144 @@
+/*
+ * 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.storage.relational.mapper.provider.h2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+class TestGroupMetaH2Provider {
+
+  private static final String DB_CONNECTION_URL = 
"jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1";
+  private static final String DB_USER = "sa";
+  private static final String DB_PASSWORD = "";
+  private static final String COLUMN_LABEL_ROLE_NAMES = "roleNames";
+  private static final String COLUMN_LABEL_ROLE_IDS = "roleIds";
+  private static final String QUERY_PARAM_METALAKE_ID = "#{metalakeId}";
+
+  private static Connection connection;
+  private static Statement statement;
+  private static GroupMetaH2Provider groupMetaH2Provider;
+
+  @BeforeAll
+  static void setUp() throws SQLException {
+    connection = DriverManager.getConnection(DB_CONNECTION_URL, DB_USER, 
DB_PASSWORD);
+    statement = connection.createStatement();
+    groupMetaH2Provider = new GroupMetaH2Provider();
+    createSchema(statement);
+  }
+
+  private static void createSchema(Statement statement) throws SQLException {
+    statement.execute(
+        "CREATE TABLE IF NOT EXISTS group_meta ("
+            + "group_id BIGINT, "
+            + "group_name VARCHAR(255), "
+            + "metalake_id BIGINT, "
+            + "audit_info VARCHAR(255), "
+            + "current_version BIGINT, "
+            + "last_version BIGINT, "
+            + "deleted_at BIGINT)");
+    statement.execute(
+        "CREATE TABLE IF NOT EXISTS role_meta ("
+            + "role_id BIGINT, "
+            + "role_name VARCHAR(255), "
+            + "deleted_at BIGINT)");
+    statement.execute(
+        "CREATE TABLE IF NOT EXISTS group_role_rel ("
+            + "group_id BIGINT, "
+            + "role_id BIGINT, "
+            + "deleted_at BIGINT)");
+  }
+
+  @Test
+  void testListExtendedGroupPOsByMetalakeIdWithoutRoles() throws SQLException {
+    statement.execute("INSERT INTO group_meta VALUES (1, 'g1', 1, 'audit', 0, 
0, 0)");
+    String sql =
+        groupMetaH2Provider
+            .listExtendedGroupPOsByMetalakeId(1L)
+            .replace(QUERY_PARAM_METALAKE_ID, "1");
+    try (ResultSet rs = statement.executeQuery(sql)) {
+      rs.next();
+      assertEquals("[]", rs.getString(COLUMN_LABEL_ROLE_NAMES));
+      assertEquals("[]", rs.getString(COLUMN_LABEL_ROLE_IDS));
+    }
+  }
+
+  @Test
+  void testListExtendedGroupPOsByMetalakeIdWithRoles() throws SQLException {
+    statement.execute("INSERT INTO group_meta VALUES (2, 'g2', 2, 'audit2', 0, 
0, 0)");
+    statement.execute("INSERT INTO role_meta VALUES (1, 'role1', 0)");
+    statement.execute("INSERT INTO role_meta VALUES (2, 'role2', 0)");
+    statement.execute("INSERT INTO group_role_rel VALUES (2, 1, 0)");
+    statement.execute("INSERT INTO group_role_rel VALUES (2, 2, 0)");
+
+    String sql =
+        groupMetaH2Provider
+            .listExtendedGroupPOsByMetalakeId(2L)
+            .replace(QUERY_PARAM_METALAKE_ID, "2");
+    try (ResultSet rs = statement.executeQuery(sql)) {
+      rs.next();
+      assertEquals("[\"role1\",\"role2\"]", 
rs.getString(COLUMN_LABEL_ROLE_NAMES));
+      assertEquals("[\"1\",\"2\"]", rs.getString(COLUMN_LABEL_ROLE_IDS));
+    }
+  }
+
+  @Test
+  void testListExtendedGroupPOsByMetalakeIdWithInvalidRoles() throws 
SQLException {
+    statement.execute("INSERT INTO group_meta VALUES (3, 'g3', 3, 'audit3', 0, 
0, 0)");
+    statement.execute("INSERT INTO role_meta VALUES (3, 'role3', 0)");
+    statement.execute("INSERT INTO role_meta VALUES (4, '', 0)");
+    statement.execute("INSERT INTO role_meta VALUES (5, null, 0)");
+    statement.execute("INSERT INTO role_meta VALUES (null, 'role6', 0)");
+    statement.execute("INSERT INTO group_role_rel VALUES (3, 3, 0)");
+    statement.execute("INSERT INTO group_role_rel VALUES (3, 4, 0)");
+    statement.execute("INSERT INTO group_role_rel VALUES (3, 5, 0)");
+    statement.execute("INSERT INTO group_role_rel VALUES (3, null, 0)");
+
+    String sql =
+        groupMetaH2Provider
+            .listExtendedGroupPOsByMetalakeId(3L)
+            .replace(QUERY_PARAM_METALAKE_ID, "3");
+    try (ResultSet rs = statement.executeQuery(sql)) {
+      rs.next();
+      assertEquals("[\"role3\"]", rs.getString(COLUMN_LABEL_ROLE_NAMES));
+      assertEquals("[\"3\",\"4\",\"5\"]", rs.getString(COLUMN_LABEL_ROLE_IDS));
+    }
+  }
+
+  @AfterAll
+  static void tearDown() throws SQLException {
+    dropSchema(statement);
+    statement.close();
+    connection.close();
+  }
+
+  private static void dropSchema(Statement statement) throws SQLException {
+    statement.execute("DROP TABLE IF EXISTS group_role_rel");
+    statement.execute("DROP TABLE IF EXISTS role_meta");
+    statement.execute("DROP TABLE IF EXISTS group_meta");
+  }
+}

Reply via email to