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

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


The following commit(s) were added to refs/heads/branch-0.6 by this push:
     new 497d5cec8 [#4169] feat(core): Supports the core logic of the ownership 
(#4363)
497d5cec8 is described below

commit 497d5cec81e48eb244af8e74131c58d72d526c53
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Aug 5 19:04:16 2024 +0800

    [#4169] feat(core): Supports the core logic of the ownership (#4363)
    
    ### What changes were proposed in this pull request?
     Supports the core logic of the ownership.
     This pull request includes two parts:
     1. Add a new type relational interface for entity store
     2. Add the ownership manager and related database ops
    
    ### Why are the changes needed?
    
    Fix: #4169
    
    ### Does this PR introduce _any_ user-facing change?
    No.
    
    ### How was this patch tested?
    Add the uts.
    
    Co-authored-by: roryqi <[email protected]>
---
 .../java/org/apache/gravitino/MetadataObject.java  |   4 +-
 .../java/org/apache/gravitino/MetadataObjects.java |   8 +-
 .../org/apache/gravitino/authorization/Owner.java  |  48 ++++
 .../apache/gravitino/authorization/Privilege.java  |   8 +-
 .../apache/gravitino/authorization/Privileges.java |  52 ++---
 .../gravitino/authorization/SecurableObject.java   |   2 +-
 .../java/org/apache/gravitino/EntityStore.java     |  10 +
 .../java/org/apache/gravitino/GravitinoEnv.java    |  11 +-
 .../gravitino/SupportsRelationOperations.java      |  67 ++++++
 .../authorization/AuthorizationUtils.java          |  67 +-----
 .../gravitino/authorization/OwnerManager.java      | 194 ++++++++++++++++
 .../gravitino/storage/relational/JDBCBackend.java  |  39 ++++
 .../storage/relational/RelationalBackend.java      |   4 +-
 .../storage/relational/RelationalEntityStore.java  |  30 ++-
 .../storage/relational/mapper/GroupMetaMapper.java |  12 +
 .../storage/relational/mapper/OwnerMetaMapper.java | 161 +++++++++++++
 .../relational/mapper/SecurableObjectMapper.java   |   2 +-
 .../storage/relational/mapper/UserMetaMapper.java  |  12 +
 .../storage/relational/po/OwnerRelPO.java          | 116 ++++++++++
 .../relational/service/CatalogMetaService.java     |  21 +-
 .../relational/service/FilesetMetaService.java     |  10 +-
 .../relational/service/GroupMetaService.java       |  17 +-
 .../relational/service/MetadataObjectService.java  |   4 +
 .../relational/service/MetalakeMetaService.java    |  32 ++-
 .../relational/service/OwnerMetaService.java       | 120 ++++++++++
 .../relational/service/RoleMetaService.java        |   9 +-
 .../relational/service/SchemaMetaService.java      |  20 +-
 .../relational/service/TableMetaService.java       |  14 +-
 .../relational/service/TopicMetaService.java       |  14 +-
 .../relational/service/UserMetaService.java        |  17 +-
 .../session/SqlSessionFactoryHelper.java           |   2 +
 .../storage/relational/utils/POConverters.java     |  29 +++
 .../apache/gravitino/utils/MetadataObjectUtil.java |   4 +
 .../apache/gravitino/utils/NameIdentifierUtil.java |   5 +
 .../gravitino/authorization/TestOwnerManager.java  | 171 ++++++++++++++
 .../storage/relational/TestJDBCBackend.java        |  73 ++++++
 .../relational/service/TestOwnerMetaService.java   | 219 ++++++++++++++++++
 .../storage/relational/utils/TestPOConverters.java |  18 ++
 core/src/test/resources/h2/schema-h2.sql           | 248 ---------------------
 scripts/h2/schema-0.6.0-h2.sql                     |  17 ++
 scripts/mysql/schema-0.6.0-mysql.sql               |  17 ++
 scripts/mysql/upgrade-0.5.0-to-0.6.0-mysql.sql     |  17 ++
 42 files changed, 1571 insertions(+), 374 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/MetadataObject.java 
b/api/src/main/java/org/apache/gravitino/MetadataObject.java
index a505b065a..534226a56 100644
--- a/api/src/main/java/org/apache/gravitino/MetadataObject.java
+++ b/api/src/main/java/org/apache/gravitino/MetadataObject.java
@@ -57,7 +57,9 @@ public interface MetadataObject {
      */
     TOPIC,
     /** A column is a sub-collection of the table that represents a group of 
same type data. */
-    COLUMN
+    COLUMN,
+    /** A role is an object contains specific securable objects with 
privileges */
+    ROLE
   }
 
   /**
diff --git a/api/src/main/java/org/apache/gravitino/MetadataObjects.java 
b/api/src/main/java/org/apache/gravitino/MetadataObjects.java
index 6bd72137e..9fb54eada 100644
--- a/api/src/main/java/org/apache/gravitino/MetadataObjects.java
+++ b/api/src/main/java/org/apache/gravitino/MetadataObjects.java
@@ -70,8 +70,9 @@ public class MetadataObjects {
     Preconditions.checkArgument(
         names.size() != 1
             || type == MetadataObject.Type.CATALOG
-            || type == MetadataObject.Type.METALAKE,
-        "If the length of names is 1, it must be the CATALOG or METALAKE 
type");
+            || type == MetadataObject.Type.METALAKE
+            || type == MetadataObject.Type.ROLE,
+        "If the length of names is 1, it must be the CATALOG, METALAKE, or 
ROLE type");
 
     Preconditions.checkArgument(
         names.size() != 2 || type == MetadataObject.Type.SCHEMA,
@@ -109,7 +110,8 @@ public class MetadataObjects {
 
     // Return null if the object is the root object
     if (object.type() == MetadataObject.Type.METALAKE
-        || object.type() == MetadataObject.Type.CATALOG) {
+        || object.type() == MetadataObject.Type.CATALOG
+        || object.type() == MetadataObject.Type.ROLE) {
       return null;
     }
 
diff --git a/api/src/main/java/org/apache/gravitino/authorization/Owner.java 
b/api/src/main/java/org/apache/gravitino/authorization/Owner.java
new file mode 100644
index 000000000..d66ad22a2
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/authorization/Owner.java
@@ -0,0 +1,48 @@
+/*
+ * 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.authorization;
+
+/**
+ * Every metadata object has an owner. The owner can have all their 
privileges. The owner could be a
+ * user or a group. The owner could be transferred to another user or group.
+ */
+public interface Owner {
+
+  /**
+   * The name of the owner.
+   *
+   * @return The name of the owner.
+   */
+  String name();
+
+  /**
+   * The type of the owner. Only supports user or group.
+   *
+   * @return The type of the owner.
+   */
+  Type type();
+
+  /** The type of the owner. */
+  enum Type {
+    /** The type of the owner is a user. */
+    USER,
+    /** The type of the owner is a group. */
+    GROUP
+  }
+}
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java 
b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
index 8ec9bb6a2..fbfde2671 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
@@ -67,10 +67,10 @@ public interface Privilege {
     PRODUCE_TOPIC(0L, 1L << 12),
     /** The privilege to consume from a topic. */
     CONSUME_TOPIC(0L, 1L << 13),
-    /** The privilege to create a user */
-    CREATE_USER(0L, 1L << 14),
-    /** The privilege to create a group */
-    CREATE_GROUP(0L, 1L << 15),
+    /** The privilege to manage users */
+    MANAGE_USERS(0L, 1L << 14),
+    /** The privilege to manage groups */
+    MANAGE_GROUPS(0L, 1L << 15),
     /** The privilege to create a role */
     CREATE_ROLE(0L, 1L << 16),
     /** The privilege to grant or revoke a role for the user or the group. */
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java 
b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
index 6947ced25..ef9e441b3 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
@@ -79,12 +79,12 @@ public class Privileges {
         return ConsumeTopic.allow();
 
         // User
-      case CREATE_USER:
-        return CreateUser.allow();
+      case MANAGE_USERS:
+        return ManageUsers.allow();
 
         // Group
-      case CREATE_GROUP:
-        return CreateGroup.allow();
+      case MANAGE_GROUPS:
+        return ManageGroups.allow();
 
         // Role
       case CREATE_ROLE:
@@ -153,12 +153,12 @@ public class Privileges {
         return ConsumeTopic.deny();
 
         // User
-      case CREATE_USER:
-        return CreateUser.deny();
+      case MANAGE_USERS:
+        return ManageUsers.deny();
 
         // Group
-      case CREATE_GROUP:
-        return CreateGroup.deny();
+      case MANAGE_GROUPS:
+        return ManageGroups.deny();
 
         // Role
       case CREATE_ROLE:
@@ -505,46 +505,46 @@ public class Privileges {
     }
   }
 
-  /** The privilege to create a user. */
-  public static class CreateUser extends GenericPrivilege<CreateUser> {
-    private static final CreateUser ALLOW_INSTANCE =
-        new CreateUser(Condition.ALLOW, Name.CREATE_USER);
-    private static final CreateUser DENY_INSTANCE =
-        new CreateUser(Condition.DENY, Name.CREATE_USER);
+  /** The privilege to manage users. */
+  public static class ManageUsers extends GenericPrivilege<ManageUsers> {
+    private static final ManageUsers ALLOW_INSTANCE =
+        new ManageUsers(Condition.ALLOW, Name.MANAGE_USERS);
+    private static final ManageUsers DENY_INSTANCE =
+        new ManageUsers(Condition.DENY, Name.MANAGE_USERS);
 
-    private CreateUser(Condition condition, Name name) {
+    private ManageUsers(Condition condition, Name name) {
       super(condition, name);
     }
 
     /** @return The instance with allow condition of the privilege. */
-    public static CreateUser allow() {
+    public static ManageUsers allow() {
       return ALLOW_INSTANCE;
     }
 
     /** @return The instance with deny condition of the privilege. */
-    public static CreateUser deny() {
+    public static ManageUsers deny() {
       return DENY_INSTANCE;
     }
   }
 
-  /** The privilege to create a group. */
-  public static class CreateGroup extends GenericPrivilege<CreateGroup> {
-    private static final CreateGroup ALLOW_INSTANCE =
-        new CreateGroup(Condition.ALLOW, Name.CREATE_GROUP);
-    private static final CreateGroup DENY_INSTANCE =
-        new CreateGroup(Condition.DENY, Name.CREATE_GROUP);
+  /** The privilege to manage groups. */
+  public static class ManageGroups extends GenericPrivilege<ManageGroups> {
+    private static final ManageGroups ALLOW_INSTANCE =
+        new ManageGroups(Condition.ALLOW, Name.MANAGE_GROUPS);
+    private static final ManageGroups DENY_INSTANCE =
+        new ManageGroups(Condition.DENY, Name.MANAGE_GROUPS);
 
-    private CreateGroup(Condition condition, Name name) {
+    private ManageGroups(Condition condition, Name name) {
       super(condition, name);
     }
 
     /** @return The instance with allow condition of the privilege. */
-    public static CreateGroup allow() {
+    public static ManageGroups allow() {
       return ALLOW_INSTANCE;
     }
 
     /** @return The instance with deny condition of the privilege. */
-    public static CreateGroup deny() {
+    public static ManageGroups deny() {
       return DENY_INSTANCE;
     }
   }
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/SecurableObject.java 
b/api/src/main/java/org/apache/gravitino/authorization/SecurableObject.java
index 760718dcf..3dc0e171b 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/SecurableObject.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/SecurableObject.java
@@ -62,7 +62,7 @@ public interface SecurableObject extends MetadataObject {
    * privileges could be `READ TABLE`, `WRITE TABLE`, etc. If a schema has the 
privilege of `LOAD
    * TABLE`. It means the role can load all tables of the schema.
    *
-   * @return The privileges of the role.
+   * @return The privileges of the securable object.
    */
   List<Privilege> privileges();
 }
diff --git a/core/src/main/java/org/apache/gravitino/EntityStore.java 
b/core/src/main/java/org/apache/gravitino/EntityStore.java
index 3693c5bbf..1112efc4b 100644
--- a/core/src/main/java/org/apache/gravitino/EntityStore.java
+++ b/core/src/main/java/org/apache/gravitino/EntityStore.java
@@ -194,4 +194,14 @@ public interface EntityStore extends Closeable {
   default SupportsTagOperations tagOperations() {
     throw new UnsupportedOperationException("tag operations are not 
supported");
   }
+
+  /**
+   * Get the extra relation operations that are supported by the entity store.
+   *
+   * @return the relation operations that are supported by the entity store
+   * @throws UnsupportedOperationException if the extra operations are not 
supported
+   */
+  default SupportsRelationOperations relationOperations() {
+    throw new UnsupportedOperationException("relation operations are not 
supported");
+  }
 }
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java 
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index a526778a2..d387c1672 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 import org.apache.gravitino.authorization.AccessControlDispatcher;
 import org.apache.gravitino.authorization.AccessControlManager;
 import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.authorization.OwnerManager;
 import org.apache.gravitino.auxiliary.AuxiliaryServiceManager;
 import org.apache.gravitino.catalog.CatalogDispatcher;
 import org.apache.gravitino.catalog.CatalogManager;
@@ -105,6 +106,7 @@ public class GravitinoEnv {
 
   private TagManager tagManager;
   private EventBus eventBus;
+  private OwnerManager ownerManager;
 
   protected GravitinoEnv() {}
 
@@ -264,9 +266,9 @@ public class GravitinoEnv {
   }
 
   /**
-   * Get the AccessControlManager associated with the Gravitino environment.
+   * Get the AccessControlDispatcher associated with the Gravitino environment.
    *
-   * @return The AccessControlManager instance.
+   * @return The AccessControlDispatcher instance.
    */
   public AccessControlDispatcher accessControlDispatcher() {
     return accessControlDispatcher;
@@ -281,6 +283,10 @@ public class GravitinoEnv {
     return tagManager;
   }
 
+  public OwnerManager ownerManager() {
+    return ownerManager;
+  }
+
   public void start() {
     auxServiceManager.serviceStart();
     metricsSystem.start();
@@ -394,6 +400,7 @@ public class GravitinoEnv {
       this.accessControlDispatcher =
           installDispatcherHooks(
               (AccessControlDispatcher) new AccessControlManager(entityStore, 
idGenerator, config));
+      this.ownerManager = new OwnerManager(entityStore);
     } else {
       this.accessControlDispatcher = null;
     }
diff --git 
a/core/src/main/java/org/apache/gravitino/SupportsRelationOperations.java 
b/core/src/main/java/org/apache/gravitino/SupportsRelationOperations.java
new file mode 100644
index 000000000..5a63ceee0
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/SupportsRelationOperations.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This is an extended interface. This is mainly used for strengthen the 
ability of querying
+ * relational data.
+ */
+public interface SupportsRelationOperations {
+
+  /** Relation is an abstraction which connects two entities. */
+  enum Type {
+    /** The owner relationship */
+    OWNER_REL
+  }
+
+  /**
+   * List the entities according to a give entity in a specific relation.
+   *
+   * @param relType The type of relation.
+   * @param nameIdentifier The given entity identifier
+   * @param identType The given entity type.
+   * @return The list of entities
+   * @throws IOException When occurs storage issues, it will throw IOException.
+   */
+  <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
+      Type relType, NameIdentifier nameIdentifier, Entity.EntityType 
identType) throws IOException;
+
+  /**
+   * insert a relation between two entities
+   *
+   * @param relType The type of relation.
+   * @param srcIdentifier The source entity identifier.
+   * @param srcType The source entity type.
+   * @param dstIdentifier The destination entity identifier.
+   * @param dstType The destination entity type.
+   * @param override If override is true, we should remove all relations of 
source entity first.
+   * @throws IOException When occurs storage issues, it will throw IOException.
+   */
+  void insertRelation(
+      Type relType,
+      NameIdentifier srcIdentifier,
+      Entity.EntityType srcType,
+      NameIdentifier dstIdentifier,
+      Entity.EntityType dstType,
+      boolean override)
+      throws IOException;
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java 
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
index 68a9cd414..875cd6fb6 100644
--- 
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
+++ 
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
@@ -24,14 +24,8 @@ import org.apache.gravitino.EntityStore;
 import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
-import org.apache.gravitino.SupportsCatalogs;
-import org.apache.gravitino.SupportsMetalakes;
-import org.apache.gravitino.connector.SupportsSchemas;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
-import org.apache.gravitino.file.FilesetCatalog;
 import org.apache.gravitino.hook.DispatcherHooks;
-import org.apache.gravitino.messaging.TopicCatalog;
-import org.apache.gravitino.rel.TableCatalog;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -124,66 +118,9 @@ public class AuthorizationUtils {
         namespace);
   }
 
-  // Install some post hooks used for ownership. The ownership will have the 
all privileges
+  // Install some post hooks used for owner. The owner will have the all 
privileges
   // of securable objects, users, groups, roles.
   public static <T> void prepareAuthorizationHooks(T manager, DispatcherHooks 
hooks) {
-    if (manager instanceof SupportsMetalakes) {
-      hooks.addPostHook(
-          "createMetalake",
-          (args, metalake) -> {
-            // TODO: Add the logic of setting the owner
-          });
-
-    } else if (manager instanceof SupportsCatalogs) {
-      hooks.addPostHook(
-          "createCatalog",
-          (args, catalog) -> {
-            // TODO: Add the logic of setting the owner
-          });
-
-    } else if (manager instanceof SupportsSchemas) {
-      hooks.addPostHook(
-          "createSchema",
-          (args, schema) -> {
-            // TODO: Add the logic of setting the owner
-          });
-
-    } else if (manager instanceof TableCatalog) {
-      hooks.addPostHook(
-          "createTable",
-          (args, schema) -> {
-            // TODO: Add the logic of setting the owner
-          });
-
-    } else if (manager instanceof TopicCatalog) {
-      hooks.addPostHook(
-          "createTopic",
-          (args, schema) -> {
-            // TODO: Add the logic of setting the owner
-          });
-
-    } else if (manager instanceof FilesetCatalog) {
-      hooks.addPostHook(
-          "createFileset",
-          (args, schema) -> {
-            // TODO: Add the logic of setting the owner
-          });
-    } else if (manager instanceof AccessControlManager) {
-      hooks.addPostHook(
-          "addUser",
-          (args, user) -> {
-            // TODO: Add the logic of setting the owner
-          });
-      hooks.addPostHook(
-          "addGroup",
-          (args, group) -> {
-            // TODO: Add the logic of setting the owner
-          });
-      hooks.addPostHook(
-          "createRole",
-          (args, role) -> {
-            // TODO: Add the logic of setting the owner
-          });
-    }
+    // TODO: Refactor the post hook by adding new dispatcher
   }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java 
b/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java
new file mode 100644
index 000000000..9fa1694e9
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java
@@ -0,0 +1,194 @@
+/*
+ * 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.authorization;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.exceptions.NoSuchEntityException;
+import org.apache.gravitino.exceptions.NotFoundException;
+import org.apache.gravitino.lock.LockType;
+import org.apache.gravitino.lock.TreeLockUtils;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.storage.kv.KvEntityStore;
+import org.apache.gravitino.utils.MetadataObjectUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * OwnerManager is used for manage the owner of metadata object. The user and 
group don't have an
+ * owner
+ */
+public class OwnerManager {
+  private static final Logger LOG = 
LoggerFactory.getLogger(OwnerManager.class);
+  private final EntityStore store;
+
+  public OwnerManager(EntityStore store) {
+    if (store instanceof KvEntityStore) {
+      String errorMsg =
+          "OwnerManager cannot run with kv entity store, please configure the 
entity "
+              + "store to use relational entity store and restart the 
Gravitino server";
+      LOG.error(errorMsg);
+      throw new RuntimeException(errorMsg);
+    } else if (store instanceof SupportsRelationOperations) {
+      this.store = store;
+    } else {
+      String errorMsg =
+          "OwnerManager currently only supports relational entity store, "
+              + "please configure the entity store to use relational entity 
store and restart the Gravitino server";
+      LOG.error(errorMsg);
+      throw new RuntimeException(errorMsg);
+    }
+  }
+
+  public void setOwner(
+      String metalake, MetadataObject metadataObject, String ownerName, 
Owner.Type ownerType) {
+    try {
+      NameIdentifier objectIdent = MetadataObjectUtil.toEntityIdent(metalake, 
metadataObject);
+      if (ownerType == Owner.Type.USER) {
+        NameIdentifier ownerIdent = AuthorizationUtils.ofUser(metalake, 
ownerName);
+        TreeLockUtils.doWithTreeLock(
+            objectIdent,
+            LockType.READ,
+            () ->
+                TreeLockUtils.doWithTreeLock(
+                    ownerIdent,
+                    LockType.READ,
+                    () -> {
+                      store
+                          .relationOperations()
+                          .insertRelation(
+                              SupportsRelationOperations.Type.OWNER_REL,
+                              objectIdent,
+                              MetadataObjectUtil.toEntityType(metadataObject),
+                              ownerIdent,
+                              Entity.EntityType.USER,
+                              true);
+                      return null;
+                    }));
+      } else if (ownerType == Owner.Type.GROUP) {
+        NameIdentifier ownerIdent = AuthorizationUtils.ofGroup(metalake, 
ownerName);
+        TreeLockUtils.doWithTreeLock(
+            objectIdent,
+            LockType.READ,
+            () ->
+                TreeLockUtils.doWithTreeLock(
+                    ownerIdent,
+                    LockType.READ,
+                    () -> {
+                      store
+                          .relationOperations()
+                          .insertRelation(
+                              SupportsRelationOperations.Type.OWNER_REL,
+                              objectIdent,
+                              MetadataObjectUtil.toEntityType(metadataObject),
+                              ownerIdent,
+                              Entity.EntityType.GROUP,
+                              true);
+                      return null;
+                    }));
+      }
+    } catch (NoSuchEntityException nse) {
+      LOG.warn(
+          "Metadata object {} or owner {} is not found", 
metadataObject.fullName(), ownerName, nse);
+      throw new NotFoundException(
+          nse, "Metadata object %s or owner %s is not found", 
metadataObject.fullName(), ownerName);
+    } catch (IOException ioe) {
+      LOG.info(
+          "Fail to set the owner {} of metadata object {}",
+          ownerName,
+          metadataObject.fullName(),
+          ioe);
+      throw new RuntimeException(ioe);
+    }
+  }
+
+  public Optional<Owner> getOwner(String metalake, MetadataObject 
metadataObject) {
+    try {
+      OwnerImpl owner = new OwnerImpl();
+      NameIdentifier ident = MetadataObjectUtil.toEntityIdent(metalake, 
metadataObject);
+      List<? extends Entity> entities =
+          TreeLockUtils.doWithTreeLock(
+              ident,
+              LockType.READ,
+              () ->
+                  store
+                      .relationOperations()
+                      .listEntitiesByRelation(
+                          SupportsRelationOperations.Type.OWNER_REL,
+                          ident,
+                          MetadataObjectUtil.toEntityType(metadataObject)));
+
+      if (entities.isEmpty()) {
+        return Optional.empty();
+      }
+
+      if (entities.size() != 1) {
+        throw new IllegalStateException(
+            String.format("The number of the owner %s must be 1", 
metadataObject.fullName()));
+      }
+
+      Entity entity = entities.get(0);
+      if (!(entity instanceof UserEntity) && !(entity instanceof GroupEntity)) 
{
+        throw new IllegalArgumentException(
+            String.format(
+                "Doesn't support owner entity class %s", 
entities.get(0).getClass().getName()));
+      }
+
+      if (entities.get(0) instanceof UserEntity) {
+        UserEntity user = (UserEntity) entities.get(0);
+        owner.name = user.name();
+        owner.type = Owner.Type.USER;
+      } else if (entities.get(0) instanceof GroupEntity) {
+        GroupEntity group = (GroupEntity) entities.get(0);
+        owner.name = group.name();
+        owner.type = Owner.Type.GROUP;
+      }
+      return Optional.of(owner);
+    } catch (NoSuchEntityException nse) {
+      throw new NotFoundException(
+          "The metadata object of %s isn't found", metadataObject.fullName());
+    } catch (IOException ioe) {
+      LOG.info("Fail to get the owner of entity {}", 
metadataObject.fullName(), ioe);
+      throw new RuntimeException(ioe);
+    }
+  }
+
+  private static class OwnerImpl implements Owner {
+
+    private String name;
+    private Type type;
+
+    @Override
+    public String name() {
+      return name;
+    }
+
+    @Override
+    public Type type() {
+      return type;
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index b635c3887..641b3be25 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -22,6 +22,7 @@ package org.apache.gravitino.storage.relational;
 import static 
org.apache.gravitino.Configs.GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
@@ -34,6 +35,7 @@ import org.apache.gravitino.HasIdentifier;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.SupportsRelationOperations;
 import org.apache.gravitino.UnsupportedEntityTypeException;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.meta.BaseMetalake;
@@ -52,6 +54,7 @@ import 
org.apache.gravitino.storage.relational.service.CatalogMetaService;
 import org.apache.gravitino.storage.relational.service.FilesetMetaService;
 import org.apache.gravitino.storage.relational.service.GroupMetaService;
 import org.apache.gravitino.storage.relational.service.MetalakeMetaService;
+import org.apache.gravitino.storage.relational.service.OwnerMetaService;
 import org.apache.gravitino.storage.relational.service.RoleMetaService;
 import org.apache.gravitino.storage.relational.service.SchemaMetaService;
 import org.apache.gravitino.storage.relational.service.TableMetaService;
@@ -360,6 +363,42 @@ public class JDBCBackend implements RelationalBackend {
         .associateTagsWithMetadataObject(objectIdent, objectType, tagsToAdd, 
tagsToRemove);
   }
 
+  @Override
+  public <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
+      SupportsRelationOperations.Type relType,
+      NameIdentifier nameIdentifier,
+      Entity.EntityType identType) {
+    switch (relType) {
+      case OWNER_REL:
+        List<E> list = Lists.newArrayList();
+        OwnerMetaService.getInstance()
+            .getOwner(nameIdentifier, identType)
+            .ifPresent(e -> list.add((E) e));
+        return list;
+      default:
+        throw new IllegalArgumentException(
+            String.format("Doesn't support the relation type %s", relType));
+    }
+  }
+
+  @Override
+  public void insertRelation(
+      SupportsRelationOperations.Type relType,
+      NameIdentifier srcIdentifier,
+      Entity.EntityType srcType,
+      NameIdentifier dstIdentifier,
+      Entity.EntityType dstType,
+      boolean override) {
+    switch (relType) {
+      case OWNER_REL:
+        OwnerMetaService.getInstance().setOwner(srcIdentifier, srcType, 
dstIdentifier, dstType);
+        break;
+      default:
+        throw new IllegalArgumentException(
+            String.format("Doesn't support the relation type %s", relType));
+    }
+  }
+
   enum JDBCBackendType {
     H2(true),
     MYSQL(false);
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
index 4521a892f..f15060e74 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
@@ -28,11 +28,13 @@ import org.apache.gravitino.EntityAlreadyExistsException;
 import org.apache.gravitino.HasIdentifier;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.SupportsRelationOperations;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.tag.SupportsTagOperations;
 
 /** Interface defining the operations for a Relation Backend. */
-public interface RelationalBackend extends Closeable, SupportsTagOperations {
+public interface RelationalBackend
+    extends Closeable, SupportsTagOperations, SupportsRelationOperations {
 
   /**
    * Initializes the Relational Backend environment with the provided 
configuration.
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
index 9d5c0b21c..7eb1432c5 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
@@ -34,6 +34,7 @@ import org.apache.gravitino.HasIdentifier;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.SupportsRelationOperations;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.meta.TagEntity;
 import org.apache.gravitino.tag.SupportsTagOperations;
@@ -46,7 +47,8 @@ import org.slf4j.LoggerFactory;
  * MySQL, PostgreSQL, etc. If you want to use a different backend, you can 
implement the {@link
  * RelationalBackend} interface
  */
-public class RelationalEntityStore implements EntityStore, 
SupportsTagOperations {
+public class RelationalEntityStore
+    implements EntityStore, SupportsTagOperations, SupportsRelationOperations {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(RelationalEntityStore.class);
   public static final ImmutableMap<String, String> RELATIONAL_BACKENDS =
       ImmutableMap.of(
@@ -141,6 +143,11 @@ public class RelationalEntityStore implements EntityStore, 
SupportsTagOperations
     return this;
   }
 
+  @Override
+  public SupportsRelationOperations relationOperations() {
+    return this;
+  }
+
   @Override
   public List<MetadataObject> 
listAssociatedMetadataObjectsForTag(NameIdentifier tagIdent)
       throws IOException {
@@ -171,4 +178,25 @@ public class RelationalEntityStore implements EntityStore, 
SupportsTagOperations
     return backend.associateTagsWithMetadataObject(
         objectIdent, objectType, tagsToAdd, tagsToRemove);
   }
+
+  @Override
+  public <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
+      SupportsRelationOperations.Type relType,
+      NameIdentifier nameIdentifier,
+      Entity.EntityType identType)
+      throws IOException {
+    return backend.listEntitiesByRelation(relType, nameIdentifier, identType);
+  }
+
+  @Override
+  public void insertRelation(
+      SupportsRelationOperations.Type relType,
+      NameIdentifier srcIdentifier,
+      Entity.EntityType srcType,
+      NameIdentifier dstIdentifier,
+      Entity.EntityType dstType,
+      boolean override)
+      throws IOException {
+    backend.insertRelation(relType, srcIdentifier, srcType, dstIdentifier, 
dstType, true);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
index 6250c2a9a..ca29a0c5c 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
@@ -60,6 +60,18 @@ public interface GroupMetaMapper {
   GroupPO selectGroupMetaByMetalakeIdAndName(
       @Param("metalakeId") Long metalakeId, @Param("groupName") String name);
 
+  @Select(
+      "SELECT group_id as groupId, group_name as groupName,"
+          + " metalake_id as metalakeId,"
+          + " audit_info as auditInfo,"
+          + " current_version as currentVersion, last_version as lastVersion,"
+          + " deleted_at as deletedAt"
+          + " FROM "
+          + GROUP_TABLE_NAME
+          + " WHERE group_id = #{groupId}"
+          + " AND deleted_at = 0")
+  GroupPO selectGroupMetaById(@Param("groupId") Long groupId);
+
   @Insert(
       "INSERT INTO "
           + GROUP_TABLE_NAME
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaMapper.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaMapper.java
new file mode 100644
index 000000000..a3dacf486
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/OwnerMetaMapper.java
@@ -0,0 +1,161 @@
+/*
+ * 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;
+
+import org.apache.gravitino.storage.relational.po.OwnerRelPO;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+/**
+ * A MyBatis Mapper for owner meta operation SQLs.
+ *
+ * <p>This interface class is a specification defined by MyBatis. It requires 
this interface class
+ * to identify the corresponding SQLs for execution. We can write SQLs in an 
additional XML file, or
+ * write SQLs with annotations in this interface Mapper. See: <a
+ * href="https://mybatis.org/mybatis-3/getting-started.html";></a>
+ */
+public interface OwnerMetaMapper {
+
+  String OWNER_TABLE_NAME = "owner_meta";
+
+  @Select(
+      "SELECT metalake_id as metalakeId,"
+          + " owner_id as ownerId,"
+          + " owner_type as ownerType,"
+          + " metadata_object_id as metadataObjectId,"
+          + " metadata_object_type as metadataObjectType,"
+          + " audit_info as auditInfo,"
+          + " current_version as currentVersion, last_version as lastVersion,"
+          + " deleted_at as deletedAt"
+          + " FROM "
+          + OWNER_TABLE_NAME
+          + " WHERE metadata_object_id = #{metadataObjectId} AND"
+          + " metadata_object_type = #{metadataObjectType}"
+          + " AND deleted_at = 0")
+  OwnerRelPO selectOwnerMetaByMetadataObjectIdAndType(
+      @Param("metadataObjectId") Long metadataObjectId,
+      @Param("metadataObjectType") String metadataObjectType);
+
+  @Insert(
+      "INSERT INTO "
+          + OWNER_TABLE_NAME
+          + "(metalake_id, metadata_object_id, metadata_object_type, owner_id, 
owner_type,"
+          + " audit_info, current_version, last_version, deleted_at)"
+          + " VALUES ("
+          + " #{ownerRelPO.metalakeId},"
+          + " #{ownerRelPO.metadataObjectId},"
+          + " #{ownerRelPO.metadataObjectType},"
+          + " #{ownerRelPO.ownerId},"
+          + " #{ownerRelPO.ownerType},"
+          + " #{ownerRelPO.auditInfo},"
+          + " #{ownerRelPO.currentVersion},"
+          + " #{ownerRelPO.lastVersion},"
+          + " #{ownerRelPO.deletedAt}"
+          + ")")
+  void insertOwnerRel(@Param("ownerRelPO") OwnerRelPO ownerRelPO);
+
+  @Update(
+      "UPDATE "
+          + OWNER_TABLE_NAME
+          + " SET deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
+          + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
+          + " WHERE metadata_object_id = #{metadataObjectId} AND 
metadata_object_type = #{metadataObjectType} AND deleted_at = 0")
+  void softDeleteOwnerRelByMetadataObjectIdAndType(
+      @Param("metadataObjectId") Long metadataObjectId,
+      @Param("metadataObjectType") String metadataObjectType);
+
+  @Update(
+      "UPDATE  "
+          + OWNER_TABLE_NAME
+          + " SET deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
+          + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
+          + " WHERE metalake_id = #{metalakeId} AND deleted_at =0")
+  void softDeleteOwnerRelByMetalakeId(@Param("metalakeId") Long metalakeId);
+
+  @Update(
+      "UPDATE  "
+          + OWNER_TABLE_NAME
+          + " ot SET ot.deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
+          + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
+          + " WHERE EXISTS ("
+          + " SELECT ct.catalog_id FROM "
+          + CatalogMetaMapper.TABLE_NAME
+          + " ct WHERE ct.catalog_id = #{catalogId}  AND ct.deleted_at = 0 AND 
ot.deleted_at = 0 AND "
+          + "ct.catalog_id = ot.metadata_object_id AND ot.metadata_object_type 
= 'CATALOG'"
+          + " UNION "
+          + " SELECT st.catalog_id FROM "
+          + SchemaMetaMapper.TABLE_NAME
+          + " st WHERE st.catalog_id = #{catalogId} AND st.deleted_at = 0 AND 
ot.deleted_at = 0 AND "
+          + "st.schema_id = ot.metadata_object_id AND ot.metadata_object_type 
= 'SCHEMA'"
+          + " UNION "
+          + " SELECT tt.catalog_id FROM "
+          + TopicMetaMapper.TABLE_NAME
+          + " tt WHERE tt.catalog_id = #{catalogId} AND tt.deleted_at = 0 AND 
ot.deleted_at = 0 AND "
+          + "tt.topic_id = ot.metadata_object_id AND ot.metadata_object_type = 
'TOPIC'"
+          + " UNION "
+          + " SELECT tat.catalog_id FROM "
+          + TableMetaMapper.TABLE_NAME
+          + " tat WHERE tat.catalog_id = #{catalogId} AND tat.deleted_at = 0 
AND ot.deleted_at = 0 AND "
+          + "tat.table_id = ot.metadata_object_id AND ot.metadata_object_type 
= 'TABLE'"
+          + " UNION "
+          + " SELECT ft.catalog_id FROM "
+          + FilesetMetaMapper.META_TABLE_NAME
+          + " ft WHERE ft.catalog_id = #{catalogId} AND ft.deleted_at = 0 AND 
ot.deleted_at = 0 AND"
+          + " ft.fileset_id = ot.metadata_object_id AND 
ot.metadata_object_type = 'FILESET'"
+          + ")")
+  void softDeleteOwnerRelByCatalogId(@Param("catalogId") Long catalogId);
+
+  @Update(
+      "UPDATE  "
+          + OWNER_TABLE_NAME
+          + " ot SET ot.deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
+          + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
+          + " WHERE EXISTS ("
+          + " SELECT st.schema_id FROM "
+          + SchemaMetaMapper.TABLE_NAME
+          + " st WHERE st.schema_id = #{schemaId} AND st.deleted_at = 0 AND 
ot.deleted_at = 0 "
+          + "AND st.schema_id = ot.metadata_object_id AND 
ot.metadata_object_type = 'SCHEMA'"
+          + " UNION "
+          + " SELECT tt.schema_id FROM "
+          + TopicMetaMapper.TABLE_NAME
+          + " tt WHERE tt.schema_id = #{schemaId} AND tt.deleted_at = 0 AND 
ot.deleted_at = 0 AND "
+          + "tt.topic_id = ot.metadata_object_id AND ot.metadata_object_type = 
'TOPIC'"
+          + " UNION "
+          + " SELECT tat.schema_id FROM "
+          + TableMetaMapper.TABLE_NAME
+          + " tat WHERE tat.schema_id = #{schemaId} AND tat.deleted_at = 0 AND 
ot.deleted_at = 0 AND "
+          + "tat.table_id = ot.metadata_object_id AND ot.metadata_object_type 
= 'TABLE'"
+          + " UNION "
+          + " SELECT ft.schema_id FROM "
+          + FilesetMetaMapper.META_TABLE_NAME
+          + " ft WHERE ft.schema_id = #{schemaId} AND ft.deleted_at = 0 AND 
ot.deleted_at = 0 AND "
+          + "ft.fileset_id = ot.metadata_object_id AND ot.metadata_object_type 
= 'FILESET'"
+          + ")")
+  void sotDeleteOwnerRelBySchemaId(@Param("schemaId") Long schemaId);
+
+  @Delete(
+      "DELETE FROM "
+          + OWNER_TABLE_NAME
+          + " WHERE deleted_at > 0 AND deleted_at < #{legacyTimeline} LIMIT 
#{limit}")
+  Integer deleteOwnerMetasByLegacyTimeline(
+      @Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit);
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/SecurableObjectMapper.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/SecurableObjectMapper.java
index 478ac363e..1a91d868c 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/SecurableObjectMapper.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/SecurableObjectMapper.java
@@ -74,7 +74,7 @@ public interface SecurableObjectMapper {
           + SECURABLE_OBJECT_TABLE_NAME
           + " ob SET ob.deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
           + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
-          + " where exists ( select * from "
+          + " WHERE exists (SELECT * from "
           + ROLE_TABLE_NAME
           + " ro WHERE ro.metalake_id = #{metalakeId} AND ro.role_id = 
ob.role_id"
           + " AND ro.deleted_at = 0) AND ob.deleted_at = 0")
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
index e7b442c09..e7c24aee6 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
@@ -60,6 +60,18 @@ public interface UserMetaMapper {
   UserPO selectUserMetaByMetalakeIdAndName(
       @Param("metalakeId") Long metalakeId, @Param("userName") String name);
 
+  @Select(
+      "SELECT user_id as userId, user_name as userName,"
+          + " metalake_id as metalakeId,"
+          + " audit_info as auditInfo,"
+          + " current_version as currentVersion, last_version as lastVersion,"
+          + " deleted_at as deletedAt"
+          + " FROM "
+          + USER_TABLE_NAME
+          + " WHERE user_id = #{userId}"
+          + " AND deleted_at = 0")
+  UserPO selectUserMetaById(@Param("userId") Long userId);
+
   @Insert(
       "INSERT INTO "
           + USER_TABLE_NAME
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/po/OwnerRelPO.java 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/OwnerRelPO.java
new file mode 100644
index 000000000..615fe592c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/OwnerRelPO.java
@@ -0,0 +1,116 @@
+/*
+ * 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.po;
+
+import com.google.common.base.Preconditions;
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+
+/** This class is the persistent object of owner relation. */
+@Getter
+public class OwnerRelPO {
+
+  Long metalakeId;
+  Long ownerId;
+  String ownerType;
+  Long metadataObjectId;
+  String metadataObjectType;
+  private String auditInfo;
+  private Long currentVersion;
+  private Long lastVersion;
+  private Long deletedAt;
+
+  private OwnerRelPO() {}
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private final OwnerRelPO ownerRelPO;
+
+    private Builder() {
+      this.ownerRelPO = new OwnerRelPO();
+    }
+
+    public Builder withMetalakeId(Long metalakeId) {
+      ownerRelPO.metalakeId = metalakeId;
+      return this;
+    }
+
+    public Builder withOwnerId(Long ownerId) {
+      ownerRelPO.ownerId = ownerId;
+      return this;
+    }
+
+    public Builder withOwnerType(String ownerType) {
+      ownerRelPO.ownerType = ownerType;
+      return this;
+    }
+
+    public Builder withMetadataObjectId(Long metadataObjectId) {
+      ownerRelPO.metadataObjectId = metadataObjectId;
+      return this;
+    }
+
+    public Builder withMetadataObjectType(String metadataObjectType) {
+      ownerRelPO.metadataObjectType = metadataObjectType;
+      return this;
+    }
+
+    public Builder withAuditIfo(String auditIfo) {
+      ownerRelPO.auditInfo = auditIfo;
+      return this;
+    }
+
+    public Builder withCurrentVersion(Long currentVersion) {
+      ownerRelPO.currentVersion = currentVersion;
+      return this;
+    }
+
+    public Builder withLastVersion(Long lastVersion) {
+      ownerRelPO.lastVersion = lastVersion;
+      return this;
+    }
+
+    public Builder withDeleteAt(Long deleteAt) {
+      ownerRelPO.deletedAt = deleteAt;
+      return this;
+    }
+
+    public OwnerRelPO build() {
+      validate();
+      return ownerRelPO;
+    }
+
+    private void validate() {
+      Preconditions.checkArgument(ownerRelPO.ownerId != null, "Owner id is 
required");
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(ownerRelPO.ownerType), "Owner type is 
required");
+      Preconditions.checkArgument(
+          ownerRelPO.metadataObjectId != null, "Metadata object id is 
required");
+      Preconditions.checkArgument(
+          ownerRelPO.metadataObjectType != null, "Metadata object type is 
required");
+      Preconditions.checkArgument(ownerRelPO.auditInfo != null, "Audit info is 
required");
+      Preconditions.checkArgument(ownerRelPO.currentVersion != null, "Current 
version is required");
+      Preconditions.checkArgument(ownerRelPO.lastVersion != null, "Last 
version is required");
+      Preconditions.checkArgument(ownerRelPO.deletedAt != null, "Deleted at is 
required");
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/CatalogMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/CatalogMetaService.java
index 6293e9b61..a6d462d42 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/CatalogMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/CatalogMetaService.java
@@ -26,6 +26,7 @@ import java.util.function.Function;
 import javax.annotation.Nullable;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
@@ -35,6 +36,7 @@ import org.apache.gravitino.meta.SchemaEntity;
 import org.apache.gravitino.storage.relational.mapper.CatalogMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.FilesetMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.FilesetVersionMapper;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.SchemaMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.TableMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.TopicMetaMapper;
@@ -220,7 +222,11 @@ public class CatalogMetaService {
           () ->
               SessionUtils.doWithoutCommit(
                   TopicMetaMapper.class,
-                  mapper -> 
mapper.softDeleteTopicMetasByCatalogId(catalogId)));
+                  mapper -> mapper.softDeleteTopicMetasByCatalogId(catalogId)),
+          () ->
+              SessionUtils.doWithoutCommit(
+                  OwnerMetaMapper.class,
+                  mapper -> mapper.softDeleteOwnerRelByCatalogId(catalogId)));
     } else {
       List<SchemaEntity> schemaEntities =
           SchemaMetaService.getInstance()
@@ -230,8 +236,17 @@ public class CatalogMetaService {
         throw new NonEmptyEntityException(
             "Entity %s has sub-entities, you should remove sub-entities 
first", identifier);
       }
-      SessionUtils.doWithCommit(
-          CatalogMetaMapper.class, mapper -> 
mapper.softDeleteCatalogMetasByCatalogId(catalogId));
+      SessionUtils.doMultipleWithCommit(
+          () ->
+              SessionUtils.doWithoutCommit(
+                  CatalogMetaMapper.class,
+                  mapper -> 
mapper.softDeleteCatalogMetasByCatalogId(catalogId)),
+          () ->
+              SessionUtils.doWithoutCommit(
+                  OwnerMetaMapper.class,
+                  mapper ->
+                      mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                          catalogId, MetadataObject.Type.CATALOG.name())));
     }
 
     return true;
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/FilesetMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/FilesetMetaService.java
index 2dca16daa..4d63c5f19 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/FilesetMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/FilesetMetaService.java
@@ -25,12 +25,14 @@ import java.util.Objects;
 import java.util.function.Function;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.meta.FilesetEntity;
 import org.apache.gravitino.storage.relational.mapper.FilesetMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.FilesetVersionMapper;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.po.FilesetMaxVersionPO;
 import org.apache.gravitino.storage.relational.po.FilesetPO;
 import org.apache.gravitino.storage.relational.utils.ExceptionUtils;
@@ -237,7 +239,13 @@ public class FilesetMetaService {
         () ->
             SessionUtils.doWithoutCommit(
                 FilesetVersionMapper.class,
-                mapper -> 
mapper.softDeleteFilesetVersionsByFilesetId(filesetId)));
+                mapper -> 
mapper.softDeleteFilesetVersionsByFilesetId(filesetId)),
+        () ->
+            SessionUtils.doWithoutCommit(
+                OwnerMetaMapper.class,
+                mapper ->
+                    mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                        filesetId, MetadataObject.Type.FILESET.name())));
 
     return true;
   }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
index 038c1b571..a1dc6283d 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -67,7 +68,7 @@ public class GroupMetaService {
     return GroupPO;
   }
 
-  private Long getGroupIdByMetalakeIdAndName(Long metalakeId, String 
groupName) {
+  public Long getGroupIdByMetalakeIdAndName(Long metalakeId, String groupName) 
{
     Long groupId =
         SessionUtils.getWithoutCommit(
             GroupMetaMapper.class,
@@ -93,6 +94,20 @@ public class GroupMetaService {
     return POConverters.fromGroupPO(groupPO, rolePOs, identifier.namespace());
   }
 
+  public GroupEntity getGroupById(String metalake, Long groupId) {
+    GroupPO groupPO =
+        SessionUtils.getWithoutCommit(
+            GroupMetaMapper.class, mapper -> 
mapper.selectGroupMetaById(groupId));
+    if (groupPO == null) {
+      throw new NoSuchEntityException(
+          NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+          Entity.EntityType.GROUP.name().toLowerCase(),
+          String.valueOf(groupId));
+    }
+    return POConverters.fromGroupPO(
+        groupPO, Collections.emptyList(), 
AuthorizationUtils.ofGroupNamespace(metalake));
+  }
+
   public void insertGroup(GroupEntity groupEntity, boolean overwritten) throws 
IOException {
     try {
       AuthorizationUtils.checkGroup(groupEntity.nameIdentifier());
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
index fbde62ac7..0ee28d029 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
@@ -49,6 +49,10 @@ public class MetadataObjectService {
     }
 
     List<String> names = DOT_SPLITTER.splitToList(fullName);
+    if (type == MetadataObject.Type.ROLE) {
+      return 
RoleMetaService.getInstance().getRoleIdByMetalakeIdAndName(metalakeId, 
names.get(0));
+    }
+
     long catalogId =
         
CatalogMetaService.getInstance().getCatalogIdByMetalakeIdAndName(metalakeId, 
names.get(0));
     if (type == MetadataObject.Type.CATALOG) {
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
index b54dc773d..d7bdb9d1f 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetalakeMetaService.java
@@ -37,6 +37,7 @@ import 
org.apache.gravitino.storage.relational.mapper.FilesetVersionMapper;
 import org.apache.gravitino.storage.relational.mapper.GroupMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.GroupRoleRelMapper;
 import org.apache.gravitino.storage.relational.mapper.MetalakeMetaMapper;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.RoleMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.SchemaMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.SecurableObjectMapper;
@@ -233,7 +234,11 @@ public class MetalakeMetaService {
             () ->
                 SessionUtils.doWithoutCommit(
                     TagMetadataObjectRelMapper.class,
-                    mapper -> 
mapper.softDeleteTagMetadataObjectRelsByMetalakeId(metalakeId)));
+                    mapper -> 
mapper.softDeleteTagMetadataObjectRelsByMetalakeId(metalakeId)),
+            () ->
+                SessionUtils.doWithoutCommit(
+                    OwnerMetaMapper.class,
+                    mapper -> 
mapper.softDeleteOwnerRelByMetalakeId(metalakeId)));
       } else {
         List<CatalogEntity> catalogEntities =
             CatalogMetaService.getInstance()
@@ -278,17 +283,30 @@ public class MetalakeMetaService {
             () ->
                 SessionUtils.doWithoutCommit(
                     TagMetadataObjectRelMapper.class,
-                    mapper -> 
mapper.softDeleteTagMetadataObjectRelsByMetalakeId(metalakeId)));
+                    mapper -> 
mapper.softDeleteTagMetadataObjectRelsByMetalakeId(metalakeId)),
+            () ->
+                SessionUtils.doWithoutCommit(
+                    OwnerMetaMapper.class,
+                    mapper -> 
mapper.softDeleteOwnerRelByMetalakeId(metalakeId)));
       }
     }
     return true;
   }
 
   public int deleteMetalakeMetasByLegacyTimeline(Long legacyTimeline, int 
limit) {
-    return SessionUtils.doWithCommitAndFetchResult(
-        MetalakeMetaMapper.class,
-        mapper -> {
-          return mapper.deleteMetalakeMetasByLegacyTimeline(legacyTimeline, 
limit);
-        });
+    int[] metalakeDeleteCount = new int[] {0};
+    int[] ownerRelDeleteCount = new int[] {0};
+    SessionUtils.doMultipleWithCommit(
+        () ->
+            metalakeDeleteCount[0] =
+                SessionUtils.doWithCommitAndFetchResult(
+                    MetalakeMetaMapper.class,
+                    mapper -> 
mapper.deleteMetalakeMetasByLegacyTimeline(legacyTimeline, limit)),
+        () ->
+            ownerRelDeleteCount[0] =
+                SessionUtils.doWithCommitAndFetchResult(
+                    OwnerMetaMapper.class,
+                    mapper -> 
mapper.deleteOwnerMetasByLegacyTimeline(legacyTimeline, limit)));
+    return metalakeDeleteCount[0] + ownerRelDeleteCount[0];
   }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/OwnerMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/OwnerMetaService.java
new file mode 100644
index 000000000..3354e7a95
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/OwnerMetaService.java
@@ -0,0 +1,120 @@
+/*
+ * 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.service;
+
+import java.util.Optional;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
+import org.apache.gravitino.storage.relational.po.OwnerRelPO;
+import org.apache.gravitino.storage.relational.utils.POConverters;
+import org.apache.gravitino.storage.relational.utils.SessionUtils;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+
+/** This class is an utilization class to retrieve owner relation. */
+public class OwnerMetaService {
+
+  private OwnerMetaService() {}
+
+  private static final OwnerMetaService INSTANCE = new OwnerMetaService();
+
+  public static OwnerMetaService getInstance() {
+    return INSTANCE;
+  }
+
+  public Optional<Entity> getOwner(NameIdentifier identifier, 
Entity.EntityType type) {
+    long metalakeId =
+        
MetalakeMetaService.getInstance().getMetalakeIdByName(getMetalake(identifier));
+    Long entityId = getEntityId(metalakeId, identifier, type);
+
+    OwnerRelPO ownerRelPO =
+        SessionUtils.getWithoutCommit(
+            OwnerMetaMapper.class,
+            mapper -> 
mapper.selectOwnerMetaByMetadataObjectIdAndType(entityId, type.name()));
+
+    if (ownerRelPO == null) {
+      return Optional.empty();
+    }
+
+    switch (Entity.EntityType.valueOf(ownerRelPO.getOwnerType())) {
+      case USER:
+        return Optional.of(
+            UserMetaService.getInstance()
+                .getUserById(getMetalake(identifier), 
ownerRelPO.getOwnerId()));
+      case GROUP:
+        return Optional.of(
+            GroupMetaService.getInstance()
+                .getGroupById(getMetalake(identifier), 
ownerRelPO.getOwnerId()));
+      default:
+        throw new IllegalArgumentException(
+            String.format("Owner type doesn't support %s", 
ownerRelPO.getOwnerType()));
+    }
+  }
+
+  public void setOwner(
+      NameIdentifier entity,
+      Entity.EntityType entityType,
+      NameIdentifier owner,
+      Entity.EntityType ownerType) {
+    long metalakeId = 
MetalakeMetaService.getInstance().getMetalakeIdByName(getMetalake(entity));
+
+    Long entityId = getEntityId(metalakeId, entity, entityType);
+    Long ownerId = getEntityId(metalakeId, owner, ownerType);
+
+    OwnerRelPO ownerRelPO =
+        POConverters.initializeOwnerRelPOsWithVersion(
+            metalakeId, ownerType.name(), ownerId, entityType.name(), 
entityId);
+    SessionUtils.doMultipleWithCommit(
+        () ->
+            SessionUtils.doWithoutCommit(
+                OwnerMetaMapper.class,
+                mapper ->
+                    mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                        entityId,
+                        NameIdentifierUtil.toMetadataObject(entity, 
entityType).type().name())),
+        () ->
+            SessionUtils.doWithoutCommit(
+                OwnerMetaMapper.class, mapper -> 
mapper.insertOwnerRel(ownerRelPO)));
+  }
+
+  private static long getEntityId(
+      long metalakeId, NameIdentifier identifier, Entity.EntityType type) {
+    switch (type) {
+      case USER:
+        return UserMetaService.getInstance()
+            .getUserIdByMetalakeIdAndName(metalakeId, identifier.name());
+      case GROUP:
+        return GroupMetaService.getInstance()
+            .getGroupIdByMetalakeIdAndName(metalakeId, identifier.name());
+      default:
+        MetadataObject object = 
NameIdentifierUtil.toMetadataObject(identifier, type);
+        return MetadataObjectService.getMetadataObjectId(
+            metalakeId, object.fullName(), object.type());
+    }
+  }
+
+  private static String getMetalake(NameIdentifier identifier) {
+    if (identifier.hasNamespace()) {
+      return identifier.namespace().level(0);
+    } else {
+      return identifier.name();
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
index 1583a943b..cbb1fdfb0 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
@@ -29,6 +29,7 @@ import org.apache.gravitino.authorization.SecurableObject;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.meta.RoleEntity;
 import org.apache.gravitino.storage.relational.mapper.GroupRoleRelMapper;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.RoleMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.SecurableObjectMapper;
 import org.apache.gravitino.storage.relational.mapper.UserRoleRelMapper;
@@ -190,7 +191,13 @@ public class RoleMetaService {
         () ->
             SessionUtils.doWithoutCommit(
                 SecurableObjectMapper.class,
-                mapper -> mapper.softDeleteSecurableObjectsByRoleId(roleId)));
+                mapper -> mapper.softDeleteSecurableObjectsByRoleId(roleId)),
+        () ->
+            SessionUtils.doWithoutCommit(
+                OwnerMetaMapper.class,
+                mapper ->
+                    mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                        roleId, MetadataObject.Type.ROLE.name())));
     return true;
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java
index 8e736a39a..f89c4c5f0 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java
@@ -25,6 +25,7 @@ import java.util.Objects;
 import java.util.function.Function;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
@@ -34,6 +35,7 @@ import org.apache.gravitino.meta.SchemaEntity;
 import org.apache.gravitino.meta.TableEntity;
 import org.apache.gravitino.storage.relational.mapper.FilesetMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.FilesetVersionMapper;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.SchemaMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.TableMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.TopicMetaMapper;
@@ -206,7 +208,10 @@ public class SchemaMetaService {
             () ->
                 SessionUtils.doWithoutCommit(
                     TopicMetaMapper.class,
-                    mapper -> 
mapper.softDeleteTopicMetasBySchemaId(schemaId)));
+                    mapper -> mapper.softDeleteTopicMetasBySchemaId(schemaId)),
+            () ->
+                SessionUtils.doWithoutCommit(
+                    OwnerMetaMapper.class, mapper -> 
mapper.sotDeleteOwnerRelBySchemaId(schemaId)));
       } else {
         List<TableEntity> tableEntities =
             TableMetaService.getInstance()
@@ -230,8 +235,17 @@ public class SchemaMetaService {
           throw new NonEmptyEntityException(
               "Entity %s has sub-entities, you should remove sub-entities 
first", identifier);
         }
-        SessionUtils.doWithCommit(
-            SchemaMetaMapper.class, mapper -> 
mapper.softDeleteSchemaMetasBySchemaId(schemaId));
+        SessionUtils.doMultipleWithCommit(
+            () ->
+                SessionUtils.doWithoutCommit(
+                    SchemaMetaMapper.class,
+                    mapper -> 
mapper.softDeleteSchemaMetasBySchemaId(schemaId)),
+            () ->
+                SessionUtils.doWithoutCommit(
+                    OwnerMetaMapper.class,
+                    mapper ->
+                        mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                            schemaId, MetadataObject.Type.SCHEMA.name())));
       }
     }
     return true;
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
index 229e7681c..87a2df50e 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
@@ -25,10 +25,12 @@ import java.util.Objects;
 import java.util.function.Function;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.TableMetaMapper;
 import org.apache.gravitino.storage.relational.po.TablePO;
 import org.apache.gravitino.storage.relational.utils.ExceptionUtils;
@@ -181,8 +183,16 @@ public class TableMetaService {
 
     Long tableId = getTableIdBySchemaIdAndName(schemaId, tableName);
 
-    SessionUtils.doWithCommit(
-        TableMetaMapper.class, mapper -> 
mapper.softDeleteTableMetasByTableId(tableId));
+    SessionUtils.doMultipleWithCommit(
+        () ->
+            SessionUtils.doWithoutCommit(
+                TableMetaMapper.class, mapper -> 
mapper.softDeleteTableMetasByTableId(tableId)),
+        () ->
+            SessionUtils.doWithoutCommit(
+                OwnerMetaMapper.class,
+                mapper ->
+                    mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                        tableId, MetadataObject.Type.TABLE.name())));
 
     return true;
   }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TopicMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TopicMetaService.java
index 02e0a5fcc..f13fc202a 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TopicMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TopicMetaService.java
@@ -25,10 +25,12 @@ import java.util.Objects;
 import java.util.function.Function;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.meta.TopicEntity;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.TopicMetaMapper;
 import org.apache.gravitino.storage.relational.po.TopicPO;
 import org.apache.gravitino.storage.relational.utils.ExceptionUtils;
@@ -194,8 +196,16 @@ public class TopicMetaService {
 
     Long topicId = getTopicIdBySchemaIdAndName(schemaId, topicName);
 
-    SessionUtils.doWithCommit(
-        TopicMetaMapper.class, mapper -> 
mapper.softDeleteTopicMetasByTopicId(topicId));
+    SessionUtils.doMultipleWithCommit(
+        () ->
+            SessionUtils.doWithoutCommit(
+                TopicMetaMapper.class, mapper -> 
mapper.softDeleteTopicMetasByTopicId(topicId)),
+        () ->
+            SessionUtils.doWithoutCommit(
+                OwnerMetaMapper.class,
+                mapper ->
+                    mapper.softDeleteOwnerRelByMetadataObjectIdAndType(
+                        topicId, MetadataObject.Type.TOPIC.name())));
 
     return true;
   }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
index a24d404b8..935ed87b9 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -67,7 +68,7 @@ public class UserMetaService {
     return userPO;
   }
 
-  private Long getUserIdByMetalakeIdAndName(Long metalakeId, String userName) {
+  public Long getUserIdByMetalakeIdAndName(Long metalakeId, String userName) {
     Long userId =
         SessionUtils.getWithoutCommit(
             UserMetaMapper.class,
@@ -153,6 +154,20 @@ public class UserMetaService {
     return true;
   }
 
+  public UserEntity getUserById(String metalake, Long userId) {
+    UserPO userPO =
+        SessionUtils.getWithoutCommit(
+            UserMetaMapper.class, mapper -> mapper.selectUserMetaById(userId));
+    if (userPO == null) {
+      throw new NoSuchEntityException(
+          NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+          Entity.EntityType.USER.name().toLowerCase(),
+          String.valueOf(userId));
+    }
+    return POConverters.fromUserPO(
+        userPO, Collections.emptyList(), 
AuthorizationUtils.ofUserNamespace(metalake));
+  }
+
   public <E extends Entity & HasIdentifier> UserEntity updateUser(
       NameIdentifier identifier, Function<E, E> updater) throws IOException {
     AuthorizationUtils.checkUser(identifier);
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/session/SqlSessionFactoryHelper.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/session/SqlSessionFactoryHelper.java
index 168c5fcd7..684252f17 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/session/SqlSessionFactoryHelper.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/session/SqlSessionFactoryHelper.java
@@ -32,6 +32,7 @@ import 
org.apache.gravitino.storage.relational.mapper.FilesetVersionMapper;
 import org.apache.gravitino.storage.relational.mapper.GroupMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.GroupRoleRelMapper;
 import org.apache.gravitino.storage.relational.mapper.MetalakeMetaMapper;
+import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.RoleMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.SchemaMetaMapper;
 import org.apache.gravitino.storage.relational.mapper.SecurableObjectMapper;
@@ -116,6 +117,7 @@ public class SqlSessionFactoryHelper {
     configuration.addMapper(SecurableObjectMapper.class);
     configuration.addMapper(TagMetaMapper.class);
     configuration.addMapper(TagMetadataObjectRelMapper.class);
+    configuration.addMapper(OwnerMetaMapper.class);
 
     // Create the SqlSessionFactory object, it is a singleton object
     if (sqlSessionFactory == null) {
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
index 490ad6112..82d739a41 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
@@ -53,6 +53,7 @@ import 
org.apache.gravitino.storage.relational.po.FilesetVersionPO;
 import org.apache.gravitino.storage.relational.po.GroupPO;
 import org.apache.gravitino.storage.relational.po.GroupRoleRelPO;
 import org.apache.gravitino.storage.relational.po.MetalakePO;
+import org.apache.gravitino.storage.relational.po.OwnerRelPO;
 import org.apache.gravitino.storage.relational.po.RolePO;
 import org.apache.gravitino.storage.relational.po.SchemaPO;
 import org.apache.gravitino.storage.relational.po.SecurableObjectPO;
@@ -1041,4 +1042,32 @@ public class POConverters {
       throw new RuntimeException("Failed to serialize json object:", e);
     }
   }
+
+  public static OwnerRelPO initializeOwnerRelPOsWithVersion(
+      Long metalakeId,
+      String ownerType,
+      Long ownerId,
+      String metadataObjectType,
+      Long metadataObjectId) {
+    try {
+      AuditInfo auditInfo =
+          AuditInfo.builder()
+              .withCreator(PrincipalUtils.getCurrentPrincipal().getName())
+              .withCreateTime(Instant.now())
+              .build();
+      return OwnerRelPO.builder()
+          .withMetalakeId(metalakeId)
+          .withOwnerId(ownerId)
+          .withOwnerType(ownerType)
+          .withMetadataObjectId(metadataObjectId)
+          .withMetadataObjectType(metadataObjectType)
+          
.withAuditIfo(JsonUtils.anyFieldMapper().writeValueAsString(auditInfo))
+          .withCurrentVersion(INIT_VERSION)
+          .withLastVersion(INIT_VERSION)
+          .withDeleteAt(DEFAULT_DELETED_AT)
+          .build();
+    } catch (JsonProcessingException e) {
+      throw new RuntimeException("Failed to serialize json object:", e);
+    }
+  }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java 
b/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
index 005456858..42878ef09 100644
--- a/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
@@ -27,6 +27,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationUtils;
 
 public class MetadataObjectUtil {
 
@@ -41,6 +42,7 @@ public class MetadataObjectUtil {
           .put(MetadataObject.Type.TOPIC, Entity.EntityType.TOPIC)
           .put(MetadataObject.Type.FILESET, Entity.EntityType.FILESET)
           .put(MetadataObject.Type.COLUMN, Entity.EntityType.COLUMN)
+          .put(MetadataObject.Type.ROLE, Entity.EntityType.ROLE)
           .build();
 
   private MetadataObjectUtil() {}
@@ -78,6 +80,8 @@ public class MetadataObjectUtil {
     switch (metadataObject.type()) {
       case METALAKE:
         return NameIdentifierUtil.ofMetalake(metalakeName);
+      case ROLE:
+        return AuthorizationUtils.ofRole(metalakeName, metadataObject.name());
       case CATALOG:
       case SCHEMA:
       case TABLE:
diff --git 
a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java 
b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
index 6afed3952..ceae3797a 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
@@ -31,6 +31,7 @@ import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.AuthorizationUtils;
 import org.apache.gravitino.exceptions.IllegalNameIdentifierException;
 import org.apache.gravitino.exceptions.IllegalNamespaceException;
 
@@ -275,6 +276,10 @@ public class NameIdentifierUtil {
         String topicParent = dot.join(ident.namespace().level(1), 
ident.namespace().level(2));
         return MetadataObjects.of(topicParent, ident.name(), 
MetadataObject.Type.TOPIC);
 
+      case ROLE:
+        AuthorizationUtils.checkRole(ident);
+        return MetadataObjects.of(null, ident.name(), 
MetadataObject.Type.ROLE);
+
       default:
         throw new IllegalArgumentException(
             "Entity type " + entityType + " is not supported to convert to 
MetadataObject");
diff --git 
a/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java 
b/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
new file mode 100644
index 000000000..57623ea89
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
@@ -0,0 +1,171 @@
+/*
+ * 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.authorization;
+
+import static org.apache.gravitino.Configs.CATALOG_CACHE_EVICTION_INTERVAL_MS;
+import static org.apache.gravitino.Configs.DEFAULT_ENTITY_RELATIONAL_STORE;
+import static 
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER;
+import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL;
+import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_STORE;
+import static org.apache.gravitino.Configs.ENTITY_STORE;
+import static org.apache.gravitino.Configs.RELATIONAL_ENTITY_STORE;
+import static org.apache.gravitino.Configs.STORE_DELETE_AFTER_TIME;
+import static org.apache.gravitino.Configs.STORE_TRANSACTION_MAX_SKEW_TIME;
+import static org.apache.gravitino.Configs.TREE_LOCK_CLEAN_INTERVAL;
+import static org.apache.gravitino.Configs.TREE_LOCK_MAX_NODE_IN_MEMORY;
+import static org.apache.gravitino.Configs.TREE_LOCK_MIN_NODE_IN_MEMORY;
+import static org.apache.gravitino.Configs.VERSION_RETENTION_COUNT;
+
+import com.google.common.collect.Lists;
+import java.io.File;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.UUID;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.EntityStoreFactory;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.lock.LockManager;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.BaseMetalake;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.SchemaVersion;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.storage.RandomIdGenerator;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestOwnerManager {
+  private static final String JDBC_STORE_PATH =
+      "/tmp/gravitino_jdbc_entityStore_" + 
UUID.randomUUID().toString().replace("-", "");
+
+  private static final String DB_DIR = JDBC_STORE_PATH + "/testdb";
+  private static final String METALAKE = "metalake_for_owner_test";
+  private static final String USER = "user";
+  private static final String GROUP = "group";
+  private static final Config config = Mockito.mock(Config.class);
+  private static EntityStore entityStore;
+
+  private static IdGenerator idGenerator;
+  private static OwnerManager ownerManager;
+
+  @BeforeAll
+  public static void setUp() throws IOException, IllegalAccessException {
+    idGenerator = new RandomIdGenerator();
+
+    File dbDir = new File(DB_DIR);
+    dbDir.mkdirs();
+
+    Mockito.when(config.get(ENTITY_STORE)).thenReturn(RELATIONAL_ENTITY_STORE);
+    
Mockito.when(config.get(ENTITY_RELATIONAL_STORE)).thenReturn(DEFAULT_ENTITY_RELATIONAL_STORE);
+    Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_URL))
+        
.thenReturn(String.format("jdbc:h2:file:%s;DB_CLOSE_DELAY=-1;MODE=MYSQL", 
DB_DIR));
+    
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER)).thenReturn("org.h2.Driver");
+    
Mockito.when(config.get(STORE_TRANSACTION_MAX_SKEW_TIME)).thenReturn(1000L);
+    Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 
1000L);
+    Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
+    
Mockito.when(config.get(CATALOG_CACHE_EVICTION_INTERVAL_MS)).thenReturn(1000L);
+
+    Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
+    Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
+    Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
+
+    FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new 
LockManager(config), true);
+
+    entityStore = EntityStoreFactory.createEntityStore(config);
+    entityStore.initialize(config);
+
+    AuditInfo audit = 
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build();
+
+    BaseMetalake metalake =
+        BaseMetalake.builder()
+            .withId(idGenerator.nextId())
+            .withName(METALAKE)
+            .withVersion(SchemaVersion.V_0_1)
+            .withComment("Test metalake")
+            .withAuditInfo(audit)
+            .build();
+    entityStore.put(metalake, false /* overwritten */);
+
+    UserEntity userEntity =
+        UserEntity.builder()
+            .withId(idGenerator.nextId())
+            .withName(USER)
+            .withRoleNames(Collections.emptyList())
+            .withRoleIds(Collections.emptyList())
+            .withNamespace(AuthorizationUtils.ofUserNamespace(METALAKE))
+            .withAuditInfo(audit)
+            .build();
+    entityStore.put(userEntity, false /* overwritten*/);
+
+    GroupEntity groupEntity =
+        GroupEntity.builder()
+            .withId(idGenerator.nextId())
+            .withName(GROUP)
+            .withRoleNames(Collections.emptyList())
+            .withRoleIds(Collections.emptyList())
+            .withNamespace(AuthorizationUtils.ofUserNamespace(METALAKE))
+            .withAuditInfo(audit)
+            .build();
+    entityStore.put(groupEntity, false /* overwritten*/);
+
+    ownerManager = new OwnerManager(entityStore);
+  }
+
+  @AfterAll
+  public static void tearDown() throws IOException {
+    if (entityStore != null) {
+      entityStore.close();
+      entityStore = null;
+    }
+
+    FileUtils.deleteDirectory(new File(JDBC_STORE_PATH));
+  }
+
+  @Test
+  public void testOwner() {
+    // Test no owner
+    MetadataObject metalakeObject =
+        MetadataObjects.of(Lists.newArrayList(METALAKE), 
MetadataObject.Type.METALAKE);
+    Assertions.assertFalse(ownerManager.getOwner(METALAKE, 
metalakeObject).isPresent());
+
+    // Test to set the user as the owner
+    ownerManager.setOwner(METALAKE, metalakeObject, USER, Owner.Type.USER);
+
+    Owner owner = ownerManager.getOwner(METALAKE, metalakeObject).get();
+    Assertions.assertEquals(USER, owner.name());
+    Assertions.assertEquals(Owner.Type.USER, owner.type());
+
+    // Test to set the group as the owner
+    ownerManager.setOwner(METALAKE, metalakeObject, GROUP, Owner.Type.GROUP);
+
+    owner = ownerManager.getOwner(METALAKE, metalakeObject).get();
+    Assertions.assertEquals(GROUP, owner.name());
+    Assertions.assertEquals(Owner.Type.GROUP, owner.type());
+  }
+}
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
index da3246255..2b7e06e78 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java
@@ -26,6 +26,7 @@ import static 
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_USER;
 import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_STORE;
 import static org.apache.gravitino.Configs.ENTITY_STORE;
 import static org.apache.gravitino.Configs.RELATIONAL_ENTITY_STORE;
+import static org.apache.gravitino.SupportsRelationOperations.Type.OWNER_REL;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -668,6 +669,55 @@ public class TestJDBCBackend {
     assertTrue(tags.contains(tag));
     assertEquals(1, tags.size());
 
+    backend.insertRelation(
+        OWNER_REL,
+        metalake.nameIdentifier(),
+        metalake.type(),
+        user.nameIdentifier(),
+        user.type(),
+        true);
+
+    backend.insertRelation(
+        OWNER_REL,
+        anotherMetaLake.nameIdentifier(),
+        anotherMetaLake.type(),
+        anotherUser.nameIdentifier(),
+        anotherUser.type(),
+        true);
+
+    backend.insertRelation(
+        OWNER_REL,
+        catalog.nameIdentifier(),
+        catalog.type(),
+        user.nameIdentifier(),
+        user.type(),
+        true);
+
+    backend.insertRelation(
+        OWNER_REL,
+        schema.nameIdentifier(),
+        schema.type(),
+        user.nameIdentifier(),
+        user.type(),
+        true);
+
+    backend.insertRelation(
+        OWNER_REL, table.nameIdentifier(), table.type(), 
user.nameIdentifier(), user.type(), true);
+
+    backend.insertRelation(
+        OWNER_REL, topic.nameIdentifier(), topic.type(), 
user.nameIdentifier(), user.type(), true);
+
+    backend.insertRelation(
+        OWNER_REL,
+        fileset.nameIdentifier(),
+        fileset.type(),
+        user.nameIdentifier(),
+        user.type(),
+        true);
+
+    backend.insertRelation(
+        OWNER_REL, role.nameIdentifier(), role.type(), user.nameIdentifier(), 
user.type(), true);
+
     // meta data soft delete
     backend.delete(metalake.nameIdentifier(), Entity.EntityType.METALAKE, 
true);
 
@@ -722,6 +772,8 @@ public class TestJDBCBackend {
     assertTrue(legacyRecordExistsInDB(user.id(), Entity.EntityType.USER));
     assertTrue(legacyRecordExistsInDB(group.id(), Entity.EntityType.GROUP));
     assertEquals(2, countRoleRels(role.id()));
+    assertEquals(7, countOwnerRel(metalake.id()));
+    assertEquals(1, countOwnerRel(anotherMetaLake.id()));
     assertEquals(2, countRoleRels(anotherRole.id()));
     assertEquals(2, listFilesetVersions(fileset.id()).size());
     assertEquals(3, listFilesetVersions(anotherFileset.id()).size());
@@ -744,6 +796,8 @@ public class TestJDBCBackend {
     assertEquals(2, countRoleRels(anotherRole.id()));
     assertEquals(0, listFilesetVersions(fileset.id()).size());
     assertFalse(legacyRecordExistsInDB(tag.id(), Entity.EntityType.TAG));
+    assertEquals(0, countOwnerRel(metalake.id()));
+    assertEquals(1, countOwnerRel(anotherMetaLake.id()));
 
     // soft delete for old version fileset
     assertEquals(3, listFilesetVersions(anotherFileset.id()).size());
@@ -869,6 +923,25 @@ public class TestJDBCBackend {
     return count;
   }
 
+  private Integer countOwnerRel(Long metalakeId) {
+    try (SqlSession sqlSession =
+            
SqlSessionFactoryHelper.getInstance().getSqlSessionFactory().openSession(true);
+        Connection connection = sqlSession.getConnection();
+        Statement statement1 = connection.createStatement();
+        ResultSet rs1 =
+            statement1.executeQuery(
+                String.format(
+                    "SELECT count(*) FROM owner_meta WHERE metalake_id = %d", 
metalakeId))) {
+      if (rs1.next()) {
+        return rs1.getInt(1);
+      } else {
+        throw new RuntimeException("Doesn't contain data");
+      }
+    } catch (SQLException se) {
+      throw new RuntimeException("SQL execution failed", se);
+    }
+  }
+
   public static BaseMetalake createBaseMakeLake(Long id, String name, 
AuditInfo auditInfo) {
     return BaseMetalake.builder()
         .withId(id)
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestOwnerMetaService.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestOwnerMetaService.java
new file mode 100644
index 000000000..5625edf62
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestOwnerMetaService.java
@@ -0,0 +1,219 @@
+/*
+ * 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.service;
+
+import java.io.IOException;
+import java.time.Instant;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.BaseMetalake;
+import org.apache.gravitino.meta.CatalogEntity;
+import org.apache.gravitino.meta.FilesetEntity;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.meta.TopicEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.storage.RandomIdGenerator;
+import org.apache.gravitino.storage.relational.TestJDBCBackend;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class TestOwnerMetaService extends TestJDBCBackend {
+
+  String metalakeName = "metalake";
+
+  private final AuditInfo auditInfo =
+      
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+
+  @Test
+  void testDifferentOwners() throws IOException {
+    BaseMetalake metalake =
+        createBaseMakeLake(RandomIdGenerator.INSTANCE.nextId(), metalakeName, 
auditInfo);
+    backend.insert(metalake, false);
+    UserEntity user =
+        createUserEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofUserNamespace(metalakeName),
+            "user",
+            auditInfo);
+    backend.insert(user, false);
+    GroupEntity group =
+        createGroupEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofGroupNamespace(metalakeName),
+            "group",
+            auditInfo);
+    backend.insert(group, false);
+
+    // Test no owner
+    Assertions.assertFalse(
+        OwnerMetaService.getInstance()
+            .getOwner(metalake.nameIdentifier(), metalake.type())
+            .isPresent());
+
+    // Test a user owner
+    OwnerMetaService.getInstance()
+        .setOwner(metalake.nameIdentifier(), metalake.type(), 
user.nameIdentifier(), user.type());
+
+    Entity entity =
+        OwnerMetaService.getInstance().getOwner(metalake.nameIdentifier(), 
metalake.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+
+    // Test a group owner
+    OwnerMetaService.getInstance()
+        .setOwner(metalake.nameIdentifier(), metalake.type(), 
group.nameIdentifier(), group.type());
+
+    entity =
+        OwnerMetaService.getInstance().getOwner(metalake.nameIdentifier(), 
metalake.type()).get();
+    Assertions.assertTrue(entity instanceof GroupEntity);
+    Assertions.assertEquals("group", ((GroupEntity) entity).name());
+  }
+
+  @Test
+  void testDifferentEntities() throws IOException {
+    String catalogName = "catalog";
+    String schemaName = "schema";
+    String tableName = "table";
+    String filesetName = "fileset";
+    String topicName = "topic";
+    String userName = "user";
+    String groupName = "group";
+    String roleName = "role";
+
+    BaseMetalake metalake =
+        createBaseMakeLake(RandomIdGenerator.INSTANCE.nextId(), metalakeName, 
auditInfo);
+    backend.insert(metalake, false);
+
+    CatalogEntity catalog =
+        createCatalog(
+            RandomIdGenerator.INSTANCE.nextId(),
+            Namespace.of(metalakeName),
+            catalogName,
+            auditInfo);
+    backend.insert(catalog, false);
+
+    SchemaEntity schema =
+        createSchemaEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            Namespace.of(metalakeName, catalogName),
+            schemaName,
+            auditInfo);
+    backend.insert(schema, false);
+
+    TableEntity table =
+        createTableEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            Namespace.of(metalakeName, catalogName, schemaName),
+            tableName,
+            auditInfo);
+    backend.insert(table, false);
+
+    TopicEntity topic =
+        createTopicEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            Namespace.of(metalakeName, catalogName, schemaName),
+            topicName,
+            auditInfo);
+    backend.insert(topic, false);
+
+    FilesetEntity fileset =
+        createFilesetEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            Namespace.of(metalakeName, catalogName, schemaName),
+            filesetName,
+            auditInfo);
+    backend.insert(fileset, false);
+
+    UserEntity user =
+        createUserEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofUserNamespace(metalakeName),
+            userName,
+            auditInfo);
+    backend.insert(user, false);
+
+    GroupEntity group =
+        createGroupEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofGroupNamespace(metalakeName),
+            groupName,
+            auditInfo);
+    backend.insert(group, false);
+
+    RoleEntity role =
+        createRoleEntity(
+            RandomIdGenerator.INSTANCE.nextId(),
+            AuthorizationUtils.ofRoleNamespace(metalakeName),
+            roleName,
+            auditInfo,
+            catalogName);
+    backend.insert(role, false);
+
+    OwnerMetaService.getInstance()
+        .setOwner(metalake.nameIdentifier(), metalake.type(), 
user.nameIdentifier(), user.type());
+    OwnerMetaService.getInstance()
+        .setOwner(catalog.nameIdentifier(), catalog.type(), 
user.nameIdentifier(), user.type());
+    OwnerMetaService.getInstance()
+        .setOwner(schema.nameIdentifier(), schema.type(), 
user.nameIdentifier(), user.type());
+    OwnerMetaService.getInstance()
+        .setOwner(fileset.nameIdentifier(), fileset.type(), 
user.nameIdentifier(), user.type());
+    OwnerMetaService.getInstance()
+        .setOwner(table.nameIdentifier(), table.type(), user.nameIdentifier(), 
user.type());
+    OwnerMetaService.getInstance()
+        .setOwner(topic.nameIdentifier(), topic.type(), user.nameIdentifier(), 
user.type());
+    OwnerMetaService.getInstance()
+        .setOwner(role.nameIdentifier(), role.type(), user.nameIdentifier(), 
user.type());
+
+    Entity entity =
+        OwnerMetaService.getInstance().getOwner(metalake.nameIdentifier(), 
metalake.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+
+    entity =
+        OwnerMetaService.getInstance().getOwner(catalog.nameIdentifier(), 
catalog.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+
+    entity = OwnerMetaService.getInstance().getOwner(schema.nameIdentifier(), 
schema.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+
+    entity = OwnerMetaService.getInstance().getOwner(table.nameIdentifier(), 
table.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+
+    entity = OwnerMetaService.getInstance().getOwner(topic.nameIdentifier(), 
topic.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+
+    entity =
+        OwnerMetaService.getInstance().getOwner(fileset.nameIdentifier(), 
fileset.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+
+    entity = OwnerMetaService.getInstance().getOwner(role.nameIdentifier(), 
role.type()).get();
+    Assertions.assertTrue(entity instanceof UserEntity);
+    Assertions.assertEquals("user", ((UserEntity) entity).name());
+  }
+}
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
index ccf445c81..3f92aafdf 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
@@ -51,6 +51,7 @@ import org.apache.gravitino.storage.relational.po.CatalogPO;
 import org.apache.gravitino.storage.relational.po.FilesetPO;
 import org.apache.gravitino.storage.relational.po.FilesetVersionPO;
 import org.apache.gravitino.storage.relational.po.MetalakePO;
+import org.apache.gravitino.storage.relational.po.OwnerRelPO;
 import org.apache.gravitino.storage.relational.po.SchemaPO;
 import org.apache.gravitino.storage.relational.po.TablePO;
 import org.apache.gravitino.storage.relational.po.TagMetadataObjectRelPO;
@@ -671,6 +672,23 @@ public class TestPOConverters {
     assertEquals(0, tagMetadataObjectRelPO.getDeletedAt());
   }
 
+  @Test
+  public void testOwnerRelPO() {
+    OwnerRelPO ownerRelPO =
+        POConverters.initializeOwnerRelPOsWithVersion(
+            1L, Entity.EntityType.USER.name(), 1L, 
Entity.EntityType.METALAKE.name(), 1L);
+
+    assertEquals(1L, ownerRelPO.getOwnerId());
+    assertEquals(1L, ownerRelPO.getMetalakeId());
+    assertEquals(1L, ownerRelPO.getMetadataObjectId());
+    assertEquals(Entity.EntityType.METALAKE.name(), 
ownerRelPO.getMetadataObjectType());
+    assertEquals(Entity.EntityType.USER.name(), ownerRelPO.getOwnerType());
+
+    assertEquals(1, ownerRelPO.getCurrentVersion());
+    assertEquals(1, ownerRelPO.getLastVersion());
+    assertEquals(0, ownerRelPO.getDeletedAt());
+  }
+
   private static BaseMetalake createMetalake(Long id, String name, String 
comment) {
     AuditInfo auditInfo =
         
AuditInfo.builder().withCreator("creator").withCreateTime(FIX_INSTANT).build();
diff --git a/core/src/test/resources/h2/schema-h2.sql 
b/core/src/test/resources/h2/schema-h2.sql
deleted file mode 100644
index 3ffa848a8..000000000
--- a/core/src/test/resources/h2/schema-h2.sql
+++ /dev/null
@@ -1,248 +0,0 @@
---
--- 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.
---
-
-CREATE TABLE IF NOT EXISTS `metalake_meta` (
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `metalake_name` VARCHAR(128) NOT NULL COMMENT 'metalake name',
-    `metalake_comment` VARCHAR(256) DEFAULT '' COMMENT 'metalake comment',
-    `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'metalake properties',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'metalake audit info',
-    `schema_version` MEDIUMTEXT NOT NULL COMMENT 'metalake schema version 
info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'metalake 
current version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'metalake last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'metalake 
deleted at',
-    PRIMARY KEY (metalake_id),
-    CONSTRAINT uk_mn_del UNIQUE (metalake_name, deleted_at)
-) ENGINE = InnoDB;
-
-
-CREATE TABLE IF NOT EXISTS `catalog_meta` (
-    `catalog_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'catalog id',
-    `catalog_name` VARCHAR(128) NOT NULL COMMENT 'catalog name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `type` VARCHAR(64) NOT NULL COMMENT 'catalog type',
-    `provider` VARCHAR(64) NOT NULL COMMENT 'catalog provider',
-    `catalog_comment` VARCHAR(256) DEFAULT '' COMMENT 'catalog comment',
-    `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'catalog properties',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'catalog audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'catalog current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'catalog last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'catalog 
deleted at',
-    PRIMARY KEY (catalog_id),
-    CONSTRAINT uk_mid_cn_del UNIQUE (metalake_id, catalog_name, deleted_at)
-) ENGINE=InnoDB;
-
-
-CREATE TABLE IF NOT EXISTS `schema_meta` (
-    `schema_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'schema id',
-    `schema_name` VARCHAR(128) NOT NULL COMMENT 'schema name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `catalog_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'catalog id',
-    `schema_comment` VARCHAR(256) DEFAULT '' COMMENT 'schema comment',
-    `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'schema properties',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'schema audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'schema current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'schema last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'schema 
deleted at',
-    PRIMARY KEY (schema_id),
-    CONSTRAINT uk_cid_sn_del UNIQUE (catalog_id, schema_name, deleted_at),
-    -- Aliases are used here, and indexes with the same name in H2 can only be 
created once.
-    KEY idx_smid (metalake_id)
-) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `table_meta` (
-    `table_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'table id',
-    `table_name` VARCHAR(128) NOT NULL COMMENT 'table name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `catalog_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'catalog id',
-    `schema_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'schema id',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'table audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'table current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'table last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'table deleted 
at',
-    PRIMARY KEY (table_id),
-    CONSTRAINT uk_sid_tn_del UNIQUE (schema_id, table_name, deleted_at),
-    -- Aliases are used here, and indexes with the same name in H2 can only be 
created once.
-    KEY idx_tmid (metalake_id),
-    KEY idx_tcid (catalog_id)
-) ENGINE=InnoDB;
-
-
-CREATE TABLE IF NOT EXISTS `fileset_meta` (
-    `fileset_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'fileset id',
-    `fileset_name` VARCHAR(128) NOT NULL COMMENT 'fileset name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `catalog_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'catalog id',
-    `schema_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'schema id',
-    `type` VARCHAR(64) NOT NULL COMMENT 'fileset type',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'fileset audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'fileset current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'fileset last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'fileset 
deleted at',
-    PRIMARY KEY (fileset_id),
-    CONSTRAINT uk_sid_fn_del UNIQUE (schema_id, fileset_name, deleted_at),
-    -- Aliases are used here, and indexes with the same name in H2 can only be 
created once.
-    KEY idx_fmid (metalake_id),
-    KEY idx_fcid (catalog_id)
-) ENGINE=InnoDB;
-
-
-CREATE TABLE IF NOT EXISTS `fileset_version_info` (
-    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `catalog_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'catalog id',
-    `schema_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'schema id',
-    `fileset_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'fileset id',
-    `version` INT UNSIGNED NOT NULL COMMENT 'fileset info version',
-    `fileset_comment` VARCHAR(256) DEFAULT '' COMMENT 'fileset comment',
-    `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'fileset properties',
-    `storage_location` MEDIUMTEXT DEFAULT NULL COMMENT 'fileset storage 
location',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'fileset 
deleted at',
-    PRIMARY KEY (id),
-    CONSTRAINT uk_fid_ver_del UNIQUE (fileset_id, version, deleted_at),
-    -- Aliases are used here, and indexes with the same name in H2 can only be 
created once.
-    KEY idx_fvmid (metalake_id),
-    KEY idx_fvcid (catalog_id)
-) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `topic_meta` (
-    `topic_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'topic id',
-    `topic_name` VARCHAR(128) NOT NULL COMMENT 'topic name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `catalog_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'catalog id',
-    `schema_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'schema id',
-    `comment` VARCHAR(256) DEFAULT '' COMMENT 'topic comment',
-    `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'topic properties',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'topic audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'topic current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'topic last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'topic deleted 
at',
-    PRIMARY KEY (topic_id),
-    CONSTRAINT uk_cid_tn_del UNIQUE (schema_id, topic_name, deleted_at),
-    -- Aliases are used here, and indexes with the same name in H2 can only be 
created once.
-    KEY idx_tvmid (metalake_id),
-    KEY idx_tvcid (catalog_id)
-) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `user_meta` (
-    `user_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'user id',
-    `user_name` VARCHAR(128) NOT NULL COMMENT 'username',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'user audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'user current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'user last version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'user deleted 
at',
-    PRIMARY KEY (`user_id`),
-    CONSTRAINT `uk_mid_us_del` UNIQUE (`metalake_id`, `user_name`, 
`deleted_at`)
-) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `role_meta` (
-    `role_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'role id',
-    `role_name` VARCHAR(128) NOT NULL COMMENT 'role name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'schema properties',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'role audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'role current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'role last version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'role deleted 
at',
-    PRIMARY KEY (`role_id`),
-    CONSTRAINT `uk_mid_rn_del` UNIQUE (`metalake_id`, `role_name`, 
`deleted_at`)
-) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `role_meta_securable_object` (
-    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
-    `role_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'role id',
-    `entity_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'securable object entity 
id',
-    `type`  VARCHAR(128) NOT NULL COMMENT 'securable object type',
-    `privilege_names` VARCHAR(256) NOT NULL COMMENT 'securable object 
privilege names',
-    `privilege_conditions` VARCHAR(256) NOT NULL COMMENT 'securable object 
privilege conditions',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'securable 
objectcurrent version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'securable object 
last version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'securable 
object deleted at',
-    PRIMARY KEY (`id`),
-    KEY `idx_obj_rid` (`role_id`),
-    KEY `idx_obj_eid` (`entity_id`)
-    ) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `user_role_rel` (
-    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
-    `user_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'user id',
-    `role_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'role id',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'relation audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'relation 
current version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'relation last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'relation 
deleted at',
-    PRIMARY KEY (`id`),
-    CONSTRAINT `uk_ui_ri_del` UNIQUE (`user_id`, `role_id`, `deleted_at`),
-    KEY `idx_rid` (`role_id`)
-) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `group_meta` (
-    `group_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'group id',
-    `group_name` VARCHAR(128) NOT NULL COMMENT 'group name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'group audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'group current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'group last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'group deleted 
at',
-    PRIMARY KEY (`group_id`),
-    CONSTRAINT `uk_mid_gr_del` UNIQUE (`metalake_id`, `group_name`, 
`deleted_at`)
-    ) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `group_role_rel` (
-    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
-    `group_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'group id',
-    `role_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'role id',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'relation audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'relation 
current version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'relation last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'relation 
deleted at',
-    PRIMARY KEY (`id`),
-    CONSTRAINT `uk_gi_ri_del` UNIQUE (`group_id`, `role_id`, `deleted_at`),
-    KEY `idx_gid` (`group_id`)
-    ) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `tag_meta` (
-    `tag_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'tag id',
-    `tag_name` VARCHAR(128) NOT NULL COMMENT 'tag name',
-    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
-    `tag_comment` VARCHAR(256) DEFAULT '' COMMENT 'tag comment',
-    `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'tag properties',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'tag audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'tag current 
version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'tag last version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'tag deleted 
at',
-    PRIMARY KEY (`tag_id`),
-    UNIQUE KEY `uk_mn_tn_del` (`metalake_id`, `tag_name`, `deleted_at`)
-    ) ENGINE=InnoDB;
-
-CREATE TABLE IF NOT EXISTS `tag_relation_meta` (
-    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
-    `tag_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'tag id',
-    `metadata_object_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metadata object 
id',
-    `metadata_object_type` VARCHAR(64) NOT NULL COMMENT 'metadata object type',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'tag relation audit info',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'tag relation 
current version',
-    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'tag relation last 
version',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'tag relation 
deleted at',
-    PRIMARY KEY (`id`),
-    UNIQUE KEY `uk_ti_mi_mo_del` (`tag_id`, `metadata_object_id`, 
`metadata_object_type`, `deleted_at`),
-    KEY `idx_tid` (`tag_id`),
-    KEY `idx_mid` (`metadata_object_id`)
-    ) ENGINE=InnoDB;
diff --git a/scripts/h2/schema-0.6.0-h2.sql b/scripts/h2/schema-0.6.0-h2.sql
index b2aad0282..9cbdcbe9e 100644
--- a/scripts/h2/schema-0.6.0-h2.sql
+++ b/scripts/h2/schema-0.6.0-h2.sql
@@ -246,3 +246,20 @@ CREATE TABLE IF NOT EXISTS `tag_relation_meta` (
     KEY `idx_tid` (`tag_id`),
     KEY `idx_mid` (`metadata_object_id`)
     ) ENGINE=InnoDB;
+
+CREATE TABLE IF NOT EXISTS `owner_meta` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
+    `owner_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'owner id',
+    `owner_type` VARCHAR(64) NOT NULL COMMENT 'owner type',
+    `metadata_object_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metadata object 
id',
+    `metadata_object_type` VARCHAR(64) NOT NULL COMMENT 'metadata object type',
+    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'owner relation audit info',
+    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'owner relation 
current version',
+    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'owner relation 
last version',
+    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'owner 
relation deleted at',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_ow_me_del` (`owner_id`, `metadata_object_id`, 
`metadata_object_type`, `deleted_at`),
+    KEY `idx_oid` (`owner_id`),
+    KEY `idx_meid` (`metadata_object_id`)
+    ) ENGINE=InnoDB;
diff --git a/scripts/mysql/schema-0.6.0-mysql.sql 
b/scripts/mysql/schema-0.6.0-mysql.sql
index b50609da3..444c2de87 100644
--- a/scripts/mysql/schema-0.6.0-mysql.sql
+++ b/scripts/mysql/schema-0.6.0-mysql.sql
@@ -238,3 +238,20 @@ CREATE TABLE IF NOT EXISTS `tag_relation_meta` (
     KEY `idx_tid` (`tag_id`),
     KEY `idx_mid` (`metadata_object_id`)
     ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'tag 
metadata object relation';
+
+CREATE TABLE IF NOT EXISTS `owner_meta` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
+    `owner_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'owner id',
+    `owner_type` VARCHAR(64) NOT NULL COMMENT 'owner type',
+    `metadata_object_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metadata object 
id',
+    `metadata_object_type` VARCHAR(64) NOT NULL COMMENT 'metadata object type',
+    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'owner relation audit info',
+    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'owner relation 
current version',
+    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'owner relation 
last version',
+    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'owner 
relation deleted at',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_ow_me_del` (`owner_id`, `metadata_object_id`, 
`metadata_object_type`,`deleted_at`),
+    KEY `idx_oid` (`owner_id`),
+    KEY `idx_meid` (`metadata_object_id`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'owner 
relation';
diff --git a/scripts/mysql/upgrade-0.5.0-to-0.6.0-mysql.sql 
b/scripts/mysql/upgrade-0.5.0-to-0.6.0-mysql.sql
index 3048c67ab..c1b358d65 100644
--- a/scripts/mysql/upgrade-0.5.0-to-0.6.0-mysql.sql
+++ b/scripts/mysql/upgrade-0.5.0-to-0.6.0-mysql.sql
@@ -64,3 +64,20 @@ CREATE TABLE IF NOT EXISTS `tag_relation_meta` (
     KEY `idx_tid` (`tag_id`),
     KEY `idx_mid` (`metadata_object_id`)
     ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'tag 
metadata object relation';
+
+CREATE TABLE IF NOT EXISTS `owner_meta` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
+    `owner_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'owner id',
+    `owner_type` VARCHAR(64) NOT NULL COMMENT 'owner type',
+    `metadata_object_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metadata object 
id',
+    `metadata_object_type` VARCHAR(64) NOT NULL COMMENT 'metadata object type',
+    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'owner relation audit info',
+    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'owner relation 
current version',
+    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'owner relation 
last version',
+    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'owner 
relation deleted at',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_ow_me_del` (`owner_id`, `metadata_object_id`, 
`metadata_object_type`, `deleted_at`),
+    KEY `idx_oid` (`owner_id`),
+    KEY `idx_meid` (`metadata_object_id`)
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'owner 
relation';

Reply via email to