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

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


The following commit(s) were added to refs/heads/main by this push:
     new 15fe57a705 [#8589] feat(client-python):  add table change to support 
alter table (#8542)
15fe57a705 is described below

commit 15fe57a705a5a0da01548514d82cfce6aea4f849
Author: George T. C. Lai <[email protected]>
AuthorDate: Mon Sep 22 15:17:52 2025 +0800

    [#8589] feat(client-python):  add table change to support alter table 
(#8542)
    
    ### What changes were proposed in this pull request?
    
    This PR is aimed at implementing the following classes corresponding to
    the Java client.
    
    TableChange.java
    
    - TableChange
    
    In this PR, the approach to implementing the above class conforms to the
    following classes for consistent coding style.
    
    - MetalakeChange (metalake_change.py)
    - SchemaChange (schema_change.py)
    - FilesetChange (fileset_change.py)
    
    ### Why are the changes needed?
    
    We need to support python client for table operations.
    
    #8589
    
    ### Does this PR introduce _any_ user-facing change?
    
    No
    
    ### How was this patch tested?
    
    Unit tests
    
    ---------
    
    Signed-off-by: George T. C. Lai <[email protected]>
---
 .../gravitino/api/expressions/literals/literal.py  |    4 +-
 .../gravitino/api/expressions/literals/literals.py |    6 +-
 .../api/expressions/transforms/transform.py        |    4 +-
 .../api/expressions/transforms/transforms.py       |    6 +-
 .../api/{expressions/indexes => rel}/__init__.py   |    0
 .../gravitino/api/{ => rel}/column.py              |    2 +-
 .../api/{types => rel/indexes}/__init__.py         |    0
 .../api/{expressions => rel}/indexes/index.py      |    0
 .../api/{expressions => rel}/indexes/indexes.py    |    2 +-
 .../partitions/identity_partition.py               |    6 +-
 .../partitions/list_partition.py                   |    4 +-
 .../{expressions => rel}/partitions/partition.py   |    0
 .../{expressions => rel}/partitions/partitions.py  |   10 +-
 .../partitions/range_partition.py                  |    2 +-
 .../gravitino/api/rel/table_change.py              | 1089 ++++++++++++++++++++
 .../{expressions/indexes => rel/types}/__init__.py |    0
 .../api/{ => rel}/types/json_serdes/__init__.py    |    4 +-
 .../types/json_serdes/_helper/serdes_utils.py      |    4 +-
 .../api/{ => rel}/types/json_serdes/base.py        |    4 +-
 .../api/{ => rel}/types/json_serdes/type_serdes.py |    6 +-
 .../gravitino/api/{ => rel}/types/type.py          |    0
 .../gravitino/api/{ => rel}/types/types.py         |    2 +-
 .../client-python/gravitino/dto/rel/column_dto.py  |    8 +-
 .../json_serdes/_helper/serdes_utils.py            |    2 +-
 .../json_serdes/column_default_value_serdes.py     |    4 +-
 .../gravitino/dto/rel/expressions/literal_dto.py   |    4 +-
 .../gravitino/dto/rel/indexes/index_dto.py         |    2 +-
 .../dto/rel/indexes/json_serdes/index_serdes.py    |    4 +-
 .../dto/rel/json_serdes/distribution_serdes.py     |    2 +-
 .../dto/rel/json_serdes/sort_order_serdes.py       |    2 +-
 .../json_serdes/partitioning_serdes.py             |    2 +-
 .../dto/rel/partitions/identity_partition_dto.py   |    2 +-
 .../partitions/json_serdes/partition_dto_serdes.py |    2 +-
 .../dto/rel/partitions/list_partition_dto.py       |    2 +-
 .../gravitino/dto/rel/partitions/partition_dto.py  |    2 +-
 .../dto/rel/partitions/range_partition_dto.py      |    2 +-
 clients/client-python/gravitino/utils/serdes.py    |    2 +-
 .../dto/rel/test_column_default_value_serdes.py    |    4 +-
 .../tests/unittests/dto/rel/test_column_dto.py     |    8 +-
 .../unittests/dto/rel/test_distribution_dto.py     |    2 +-
 .../unittests/dto/rel/test_field_reference_dto.py  |    2 +-
 .../unittests/dto/rel/test_func_expression_dto.py  |    2 +-
 .../tests/unittests/dto/rel/test_function_arg.py   |    2 +-
 .../tests/unittests/dto/rel/test_index_dto.py      |    2 +-
 .../tests/unittests/dto/rel/test_index_serdes.py   |    2 +-
 .../tests/unittests/dto/rel/test_literal_dto.py    |    2 +-
 .../rel/test_non_single_field_partitioning_dto.py  |    2 +-
 .../unittests/dto/rel/test_partition_dto_serdes.py |    2 +-
 .../tests/unittests/dto/rel/test_partition_dtos.py |    2 +-
 .../unittests/dto/rel/test_partition_utils.py      |    2 +-
 .../tests/unittests/dto/rel/test_partitioning.py   |    2 +-
 .../unittests/dto/rel/test_partitioning_serdes.py  |    2 +-
 .../tests/unittests/dto/rel/test_serdes_utils.py   |    2 +-
 .../dto/rel/test_single_field_partitioning_dto.py  |    2 +-
 .../tests/unittests/dto/rel/test_sort_order_dto.py |    2 +-
 .../unittests/dto/rel/test_sort_order_serdes.py    |    2 +-
 .../dto/rel/test_unparsed_expression_dto.py        |    2 +-
 .../unittests/json_serdes/test_type_serdes.py      |    8 +-
 .../tests/unittests/rel/test_indexes.py            |    4 +-
 .../tests/unittests/rel/test_literals.py           |    4 +-
 .../tests/unittests/rel/test_partitions.py         |    2 +-
 .../tests/unittests/rel/test_table_change.py       |  566 ++++++++++
 .../tests/unittests/rel/test_transforms.py         |    2 +-
 .../tests/unittests/rel/test_types.py              |    2 +-
 .../client-python/tests/unittests/test_column.py   |    4 +-
 65 files changed, 1746 insertions(+), 91 deletions(-)

diff --git 
a/clients/client-python/gravitino/api/expressions/literals/literal.py 
b/clients/client-python/gravitino/api/expressions/literals/literal.py
index 676b9ef4ce..b1edbc5068 100644
--- a/clients/client-python/gravitino/api/expressions/literals/literal.py
+++ b/clients/client-python/gravitino/api/expressions/literals/literal.py
@@ -16,10 +16,10 @@
 # under the License.
 
 from abc import abstractmethod
-from typing import List, TypeVar, Generic
+from typing import Generic, List, TypeVar
 
 from gravitino.api.expressions.expression import Expression
-from gravitino.api.types.type import Type
+from gravitino.api.rel.types.type import Type
 
 T = TypeVar("T")
 
diff --git 
a/clients/client-python/gravitino/api/expressions/literals/literals.py 
b/clients/client-python/gravitino/api/expressions/literals/literals.py
index c4d07338bc..e5c85f1568 100644
--- a/clients/client-python/gravitino/api/expressions/literals/literals.py
+++ b/clients/client-python/gravitino/api/expressions/literals/literals.py
@@ -15,12 +15,12 @@
 # specific language governing permissions and limitations
 # under the License.
 import decimal
+from datetime import date, datetime, time
 from typing import TypeVar
-from datetime import date, time, datetime
 
 from gravitino.api.expressions.literals.literal import Literal
-from gravitino.api.types.type import Type
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.type import Type
+from gravitino.api.rel.types.types import Types
 
 T = TypeVar("T")
 
diff --git 
a/clients/client-python/gravitino/api/expressions/transforms/transform.py 
b/clients/client-python/gravitino/api/expressions/transforms/transform.py
index ad47be5255..e653be0988 100644
--- a/clients/client-python/gravitino/api/expressions/transforms/transform.py
+++ b/clients/client-python/gravitino/api/expressions/transforms/transform.py
@@ -20,8 +20,8 @@ from typing import List
 
 from gravitino.api.expressions.expression import Expression
 from gravitino.api.expressions.named_reference import NamedReference
-from gravitino.api.expressions.partitions.partition import Partition
-from gravitino.api.expressions.partitions.partitions import Partitions
+from gravitino.api.rel.partitions.partition import Partition
+from gravitino.api.rel.partitions.partitions import Partitions
 
 
 class Transform(Expression, ABC):
diff --git 
a/clients/client-python/gravitino/api/expressions/transforms/transforms.py 
b/clients/client-python/gravitino/api/expressions/transforms/transforms.py
index c32935659f..8d587ead45 100644
--- a/clients/client-python/gravitino/api/expressions/transforms/transforms.py
+++ b/clients/client-python/gravitino/api/expressions/transforms/transforms.py
@@ -21,13 +21,13 @@ from gravitino.api.expressions.expression import Expression
 from gravitino.api.expressions.literals.literal import Literal
 from gravitino.api.expressions.literals.literals import Literals
 from gravitino.api.expressions.named_reference import NamedReference
-from gravitino.api.expressions.partitions.list_partition import ListPartition
-from gravitino.api.expressions.partitions.partition import Partition
-from gravitino.api.expressions.partitions.range_partition import RangePartition
 from gravitino.api.expressions.transforms.transform import (
     SingleFieldTransform,
     Transform,
 )
+from gravitino.api.rel.partitions.list_partition import ListPartition
+from gravitino.api.rel.partitions.partition import Partition
+from gravitino.api.rel.partitions.range_partition import RangePartition
 
 
 class Transforms(Transform):
diff --git 
a/clients/client-python/gravitino/api/expressions/indexes/__init__.py 
b/clients/client-python/gravitino/api/rel/__init__.py
similarity index 100%
copy from clients/client-python/gravitino/api/expressions/indexes/__init__.py
copy to clients/client-python/gravitino/api/rel/__init__.py
diff --git a/clients/client-python/gravitino/api/column.py 
b/clients/client-python/gravitino/api/rel/column.py
similarity index 99%
rename from clients/client-python/gravitino/api/column.py
rename to clients/client-python/gravitino/api/rel/column.py
index e1d782732f..b540a23c22 100644
--- a/clients/client-python/gravitino/api/column.py
+++ b/clients/client-python/gravitino/api/rel/column.py
@@ -23,8 +23,8 @@ from typing import Optional
 
 from gravitino.api.expressions.expression import Expression
 from gravitino.api.expressions.function_expression import FunctionExpression
+from gravitino.api.rel.types.type import Type
 from gravitino.api.tag.supports_tags import SupportsTags
-from gravitino.api.types.type import Type
 from gravitino.exceptions.base import UnsupportedOperationException
 from gravitino.utils.precondition import Precondition
 
diff --git a/clients/client-python/gravitino/api/types/__init__.py 
b/clients/client-python/gravitino/api/rel/indexes/__init__.py
similarity index 100%
rename from clients/client-python/gravitino/api/types/__init__.py
rename to clients/client-python/gravitino/api/rel/indexes/__init__.py
diff --git a/clients/client-python/gravitino/api/expressions/indexes/index.py 
b/clients/client-python/gravitino/api/rel/indexes/index.py
similarity index 100%
rename from clients/client-python/gravitino/api/expressions/indexes/index.py
rename to clients/client-python/gravitino/api/rel/indexes/index.py
diff --git a/clients/client-python/gravitino/api/expressions/indexes/indexes.py 
b/clients/client-python/gravitino/api/rel/indexes/indexes.py
similarity index 97%
rename from clients/client-python/gravitino/api/expressions/indexes/indexes.py
rename to clients/client-python/gravitino/api/rel/indexes/indexes.py
index 720e2b46fe..7862bc342d 100644
--- a/clients/client-python/gravitino/api/expressions/indexes/indexes.py
+++ b/clients/client-python/gravitino/api/rel/indexes/indexes.py
@@ -18,7 +18,7 @@
 
 from typing import ClassVar, List, Optional, final
 
-from gravitino.api.expressions.indexes.index import Index
+from gravitino.api.rel.indexes.index import Index
 
 
 class Indexes:
diff --git 
a/clients/client-python/gravitino/api/expressions/partitions/identity_partition.py
 b/clients/client-python/gravitino/api/rel/partitions/identity_partition.py
similarity index 91%
rename from 
clients/client-python/gravitino/api/expressions/partitions/identity_partition.py
rename to 
clients/client-python/gravitino/api/rel/partitions/identity_partition.py
index e4b660c09d..75e175dedc 100644
--- 
a/clients/client-python/gravitino/api/expressions/partitions/identity_partition.py
+++ b/clients/client-python/gravitino/api/rel/partitions/identity_partition.py
@@ -16,10 +16,10 @@
 # under the License.
 
 from abc import abstractmethod
-from typing import List, Any
+from typing import Any, List
 
-from .partition import Partition
-from ..literals.literal import Literal
+from gravitino.api.expressions.literals.literal import Literal
+from gravitino.api.rel.partitions.partition import Partition
 
 
 class IdentityPartition(Partition):
diff --git 
a/clients/client-python/gravitino/api/expressions/partitions/list_partition.py 
b/clients/client-python/gravitino/api/rel/partitions/list_partition.py
similarity index 94%
rename from 
clients/client-python/gravitino/api/expressions/partitions/list_partition.py
rename to clients/client-python/gravitino/api/rel/partitions/list_partition.py
index 8316e4daa0..4f30020ee8 100644
--- 
a/clients/client-python/gravitino/api/expressions/partitions/list_partition.py
+++ b/clients/client-python/gravitino/api/rel/partitions/list_partition.py
@@ -16,10 +16,10 @@
 # under the License.
 
 from abc import abstractmethod
-from typing import List, Any
+from typing import Any, List
 
 from gravitino.api.expressions.literals.literal import Literal
-from gravitino.api.expressions.partitions.partition import Partition
+from gravitino.api.rel.partitions.partition import Partition
 
 
 class ListPartition(Partition):
diff --git 
a/clients/client-python/gravitino/api/expressions/partitions/partition.py 
b/clients/client-python/gravitino/api/rel/partitions/partition.py
similarity index 100%
rename from 
clients/client-python/gravitino/api/expressions/partitions/partition.py
rename to clients/client-python/gravitino/api/rel/partitions/partition.py
diff --git 
a/clients/client-python/gravitino/api/expressions/partitions/partitions.py 
b/clients/client-python/gravitino/api/rel/partitions/partitions.py
similarity index 94%
rename from 
clients/client-python/gravitino/api/expressions/partitions/partitions.py
rename to clients/client-python/gravitino/api/rel/partitions/partitions.py
index c116b3bdcb..10b2a43698 100644
--- a/clients/client-python/gravitino/api/expressions/partitions/partitions.py
+++ b/clients/client-python/gravitino/api/rel/partitions/partitions.py
@@ -15,13 +15,13 @@
 # specific language governing permissions and limitations
 # under the License.
 
-from typing import List, Dict, Any, Optional
+from typing import Any, Dict, List, Optional
 
 from gravitino.api.expressions.literals.literal import Literal
-from gravitino.api.expressions.partitions.identity_partition import 
IdentityPartition
-from gravitino.api.expressions.partitions.list_partition import ListPartition
-from gravitino.api.expressions.partitions.partition import Partition
-from gravitino.api.expressions.partitions.range_partition import RangePartition
+from gravitino.api.rel.partitions.identity_partition import IdentityPartition
+from gravitino.api.rel.partitions.list_partition import ListPartition
+from gravitino.api.rel.partitions.partition import Partition
+from gravitino.api.rel.partitions.range_partition import RangePartition
 
 
 class Partitions:
diff --git 
a/clients/client-python/gravitino/api/expressions/partitions/range_partition.py 
b/clients/client-python/gravitino/api/rel/partitions/range_partition.py
similarity index 96%
rename from 
clients/client-python/gravitino/api/expressions/partitions/range_partition.py
rename to clients/client-python/gravitino/api/rel/partitions/range_partition.py
index 7155c033c0..b3f4e0af80 100644
--- 
a/clients/client-python/gravitino/api/expressions/partitions/range_partition.py
+++ b/clients/client-python/gravitino/api/rel/partitions/range_partition.py
@@ -19,7 +19,7 @@ from abc import abstractmethod
 from typing import Any
 
 from gravitino.api.expressions.literals.literal import Literal
-from gravitino.api.expressions.partitions.partition import Partition
+from gravitino.api.rel.partitions.partition import Partition
 
 
 class RangePartition(Partition):
diff --git a/clients/client-python/gravitino/api/rel/table_change.py 
b/clients/client-python/gravitino/api/rel/table_change.py
new file mode 100644
index 0000000000..8c1de696a8
--- /dev/null
+++ b/clients/client-python/gravitino/api/rel/table_change.py
@@ -0,0 +1,1089 @@
+# 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.
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from typing import Optional, cast, final
+
+from dataclasses_json import config
+
+from gravitino.api.expressions.expression import Expression
+from gravitino.api.rel.column import Column
+from gravitino.api.rel.indexes.index import Index
+from gravitino.api.rel.types.type import Type
+
+
+class TableChange(ABC):
+    """Defines the public APIs for managing tables in a schema.
+
+    The `TableChange` interface defines the public API for managing tables in 
a schema.
+    If the catalog implementation supports tables, it must implement this 
interface.
+    """
+
+    @staticmethod
+    def rename(new_name: str) -> "RenameTable":
+        """Create a `TableChange` for renaming a table.
+
+        Args:
+            new_name: The new table name.
+
+        Returns:
+            RenameTable: A `TableChange` for the rename.
+        """
+        return TableChange.RenameTable(new_name)
+
+    @staticmethod
+    def update_comment(new_comment: str) -> "UpdateComment":
+        """Create a `TableChange` for updating the comment.
+
+        Args:
+            new_comment: The new comment.
+
+        Returns:
+            UpdateComment: A `TableChange` for the update.
+        """
+        return TableChange.UpdateComment(new_comment)
+
+    @staticmethod
+    def set_property(property_name: str, value: str) -> "SetProperty":
+        """Create a `TableChange` for setting a table property.
+
+        If the property already exists, it will be replaced with the new value.
+
+        Args:
+            property_name (str): The property name.
+            value (str): The new property value.
+
+        Returns:
+            SetProperty: A `TableChange` for the addition.
+        """
+        return TableChange.SetProperty(property_name, value)
+
+    @staticmethod
+    def remove_property(property_name: str) -> "RemoveProperty":
+        """Create a `TableChange` for removing a table property.
+
+        If the property does not exist, the change will succeed.
+
+        Args:
+            property_name (str): The property name.
+
+        Returns:
+            RemoveProperty: A `TableChange` for the addition.
+        """
+        return TableChange.RemoveProperty(property_name)
+
+    @staticmethod
+    def add_column(
+        field_name: list[str],
+        data_type: Type,
+        comment: Optional[str] = None,
+        position: Optional["TableChange.ColumnPosition"] = None,
+        nullable: bool = True,
+        auto_increment: bool = False,
+        default_value: Optional[Expression] = None,
+    ) -> "AddColumn":
+        """Create a `TableChange` for adding a column.
+
+        Args:
+            field_name (list[str]):
+                Field name of the new column.
+            data_type (Type):
+                The new column's data type.
+            comment (Optional[str]):
+                The new field's comment string, defaults to `None`.
+            position (Optional[TableChange.ColumnPosition]):
+                The new column's position, defaults to `None`.
+            nullable (bool):
+                The new column's nullable.
+            auto_increment (bool):
+                The new column's autoIncrement.
+            default_value (Expression):
+                The new column's default value.
+
+        Returns:
+            AddColumn: A `TableChange` for the addition.
+        """
+        return AddColumn(
+            field_name,
+            data_type,
+            comment,
+            position,
+            nullable,
+            auto_increment,
+            default_value,
+        )
+
+    @staticmethod
+    def rename_column(field_name: list[str], new_name: str) -> "RenameColumn":
+        """Create a `TableChange` for renaming a field.
+
+        The name is used to find the field to rename. The new name will 
replace the **leaf field
+        name**. For example, `rename_column(["a", "b", "c"], "x")` should 
produce column **a.b.x**.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`.
+
+        Args:
+            field_name (list[str]): The current field name.
+            new_name (str): The new name.
+
+        Returns:
+            RenameColumn: A TableChange for the rename.
+        """
+        return RenameColumn(field_name, new_name)
+
+    @staticmethod
+    def update_column_default_value(
+        field_name: list[str], new_default_value: Expression
+    ) -> "UpdateColumnDefaultValue":
+        """Create a `TableChange` for updating the default value of a field.
+
+        The name is used to find the field to update.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`.
+
+        Args:
+            field_name (list[str]): The field name of the column to update.
+            new_default_value (Expression): The new default value.
+
+        Returns:
+            TableChange: A `TableChange` for the update.
+        """
+        return UpdateColumnDefaultValue(field_name, new_default_value)
+
+    @staticmethod
+    def update_column_type(
+        field_name: list[str], new_data_type: Type
+    ) -> "UpdateColumnType":
+        """Create a `TableChange` for updating the type of a field that is 
nullable.
+
+        The field name are used to find the field to update.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`.
+
+        Args:
+            field_name (list[str]): The field name of the column to update.
+            new_data_type (Type): The new data type.
+
+        Returns:
+            TableChange: A `TableChange` for the update.
+        """
+        return UpdateColumnType(field_name, new_data_type)
+
+    @staticmethod
+    def update_column_comment(
+        field_name: list[str], new_comment: str
+    ) -> "UpdateColumnComment":
+        """Create a `TableChange` for updating the comment of a field.
+
+        The name is used to find the field to update.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`.
+
+        Args:
+            field_name (list[str]): The field name of the column to update.
+            new_comment (str): The new comment.
+
+        Returns:
+            TableChange: A `TableChange` for the update.
+        """
+        return UpdateColumnComment(field_name, new_comment)
+
+    @staticmethod
+    def update_column_position(
+        field_name: list[str], new_position: "TableChange.ColumnPosition"
+    ) -> "UpdateColumnPosition":
+        """Create a `TableChange` for updating the position of a field.
+
+        The name is used to find the field to update.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`.
+
+        Args:
+            field_name (list[str]): The field name of the column to update.
+            new_position (TableChange.ColumnPosition): The new position.
+
+        Returns:
+            TableChange: A `TableChange` for the update.
+        """
+        return UpdateColumnPosition(field_name, new_position)
+
+    @staticmethod
+    def delete_column(field_name: list[str], if_exists: bool) -> 
"DeleteColumn":
+        """Create a `TableChange` for deleting a field.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`
+        unless `if_exists` is true.
+
+        Args:
+            field_name (list[str]): Field name of the column to delete.
+            if_exists (bool): If true, silence the error if column does not 
exist during drop.
+                Otherwise, an `IllegalArgumentException` will be thrown.
+
+        Returns:
+            TableChange: A `TableChange` for the delete.
+        """
+        return DeleteColumn(field_name, if_exists)
+
+    @staticmethod
+    def update_column_nullability(
+        field_name: list[str], nullable: bool
+    ) -> "UpdateColumnNullability":
+        """Create a `TableChange` for updating the nullability of a field.
+
+        The name is used to find the field to update.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`.
+
+        Args:
+            field_name (list[str]): The field name of the column to update.
+            nullable (bool): The new nullability.
+
+        Returns:
+            TableChange: A `TableChange` for the update.
+        """
+        return UpdateColumnNullability(field_name, nullable)
+
+    @staticmethod
+    def add_index(
+        index_type: Index.IndexType,
+        name: str,
+        field_names: list[list[str]],
+    ) -> "AddIndex":
+        """Create a `TableChange` for adding an index.
+
+        Args:
+            index_type (Index.IndexType): The type of the index.
+            name (str): The name of the index.
+            field_names (list[list[str]]): The field names of the index.
+
+        Returns:
+            TableChange: A `TableChange` for the add index.
+        """
+        return TableChange.AddIndex(index_type, name, field_names)
+
+    @staticmethod
+    def delete_index(name: str, if_exists: bool) -> "DeleteIndex":
+        """Create a `TableChange` for deleting an index.
+
+        Args:
+            name (str): The name of the index to be dropped.
+            if_exists (bool): If true, silence the error if column does not 
exist during drop.
+                Otherwise, an `IllegalArgumentException` will be thrown.
+
+        Returns:
+            TableChange: A `TableChange` for the delete index.
+        """
+        return TableChange.DeleteIndex(name, if_exists)
+
+    @staticmethod
+    def update_column_auto_increment(
+        field_name: list[str], auto_increment: bool
+    ) -> "UpdateColumnAutoIncrement":
+        """Create a `TableChange` for updating the autoIncrement of a field.
+
+        The name is used to find the field to update.
+
+        If the field does not exist, the change will result in an 
`IllegalArgumentException`.
+
+        Args:
+            field_name (list[str]): The field name of the column to update.
+            auto_increment (bool): The new autoIncrement.
+
+        Returns:
+            TableChange: A `TableChange` for the update.
+        """
+        return UpdateColumnAutoIncrement(field_name, auto_increment)
+
+    @final
+    @dataclass(frozen=True)
+    class RenameTable:
+        """A `TableChange` to rename a table."""
+
+        _new_name: str = field(metadata=config(field_name="new_name"))
+
+        def get_new_name(self) -> str:
+            """Retrieves the new name for the table.
+
+            Returns:
+                str: The new name of the table.
+            """
+            return self._new_name
+
+        def __str__(self):
+            return f"RENAMETABLE {self._new_name}"
+
+        def __eq__(self, value: object) -> bool:
+            if not isinstance(value, TableChange.RenameTable):
+                return False
+            other = cast(TableChange.RenameTable, value)
+            return self._new_name == other.get_new_name()
+
+        def __hash__(self) -> int:
+            return hash(self._new_name)
+
+    @final
+    @dataclass(frozen=True)
+    class UpdateComment:
+        """A `TableChange` to update a table's comment."""
+
+        _new_comment: str = field(metadata=config(field_name="new_comment"))
+
+        def get_new_comment(self) -> str:
+            """Retrieves the new comment for the table.
+
+            Returns:
+                str: The new comment of the table.
+            """
+            return self._new_comment
+
+        def __str__(self):
+            return f"UPDATECOMMENT {self._new_comment}"
+
+        def __eq__(self, value: object) -> bool:
+            if not isinstance(value, TableChange.UpdateComment):
+                return False
+            other = cast(TableChange.UpdateComment, value)
+            return self._new_comment == other.get_new_comment()
+
+        def __hash__(self) -> int:
+            return hash(self._new_comment)
+
+    @final
+    @dataclass(frozen=True)
+    class SetProperty:
+        """A `TableChange` to set a table property."""
+
+        _property: str = field(metadata=config(field_name="property"))
+        _value: str = field(metadata=config(field_name="value"))
+
+        def get_property(self) -> str:
+            """Retrieves the name of the property.
+
+            Returns:
+                str: The name of the property.
+            """
+            return self._property
+
+        def get_value(self) -> str:
+            """Retrieves the value of the property.
+
+            Returns:
+                str: The value of the property.
+            """
+            return self._value
+
+        def __str__(self):
+            return f"SETPROPERTY {self._property} {self._value}"
+
+        def __eq__(self, value: object) -> bool:
+            if not isinstance(value, TableChange.SetProperty):
+                return False
+            other = cast(TableChange.SetProperty, value)
+            return (
+                self._property == other.get_property()
+                and self._value == other.get_value()
+            )
+
+        def __hash__(self) -> int:
+            return hash((self._property, self._value))
+
+    @final
+    @dataclass(frozen=True)
+    class RemoveProperty:
+        """A `TableChange` to remove a table property.
+
+        If the property does not exist, the change should succeed.
+        """
+
+        _property: str = field(metadata=config(field_name="property"))
+
+        def get_property(self) -> str:
+            """Retrieves the name of the property to be removed from the table.
+
+            Returns:
+                str: The name of the property scheduled for removal.
+            """
+            return self._property
+
+        def __str__(self):
+            return f"REMOVEPROPERTY {self._property}"
+
+        def __eq__(self, value: object) -> bool:
+            if not isinstance(value, TableChange.RemoveProperty):
+                return False
+            other = cast(TableChange.RemoveProperty, value)
+            return self._property == other.get_property()
+
+        def __hash__(self) -> int:
+            return hash(self._property)
+
+    @final
+    @dataclass(frozen=True)
+    class AddIndex:
+        """A TableChange to add an index.
+
+        Add an index key based on the type and field name passed in as well as 
the name.
+        """
+
+        _type: Index.IndexType = field(metadata=config(field_name="type"))
+        _name: str = field(metadata=config(field_name="name"))
+        _field_names: list[list[str]] = 
field(metadata=config(field_name="field_names"))
+
+        def get_type(self) -> Index.IndexType:
+            """Retrieves the type of the index.
+
+            Returns:
+                IndexType: The type of the index.
+            """
+            return self._type
+
+        def get_name(self) -> str:
+            """Retrieves the name of the index.
+
+            Returns:
+                str: The name of the index.
+            """
+            return self._name
+
+        def get_field_names(self) -> list[list[str]]:
+            """Retrieves the field names of the index.
+
+            Returns:
+                list[list[str]]: The field names of the index.
+            """
+            return self._field_names
+
+        def __eq__(self, value: object) -> bool:
+            if not isinstance(value, TableChange.AddIndex):
+                return False
+            other = cast(TableChange.AddIndex, value)
+            return (
+                self._type == other.get_type()
+                and self._name == other.get_name()
+                and self._field_names == other.get_field_names()
+            )
+
+        def __hash__(self) -> int:
+            return 31 * hash((self._type, self._name)) + hash(
+                tuple(tuple(field_name) for field_name in self._field_names)
+            )
+
+    @final
+    @dataclass(frozen=True)
+    class DeleteIndex:
+        """A `TableChange` to delete an index.
+
+        If the index does not exist, the change must result in an 
`IllegalArgumentException`.
+        """
+
+        _name: str = field(metadata=config(field_name="name"))
+        _if_exists: bool = field(metadata=config(field_name="if_exists"))
+
+        def get_name(self) -> str:
+            """Retrieves the name of the index to be deleted.
+
+            Returns:
+                str: The name of the index to be deleted.
+            """
+            return self._name
+
+        def is_if_exists(self) -> bool:
+            """Retrieves the value of the `if_exists` flag.
+
+            Returns:
+                bool: True if the index should be deleted if it exists, False 
otherwise.
+            """
+            return self._if_exists
+
+        def __eq__(self, value: object) -> bool:
+            if not isinstance(value, TableChange.DeleteIndex):
+                return False
+            other = cast(TableChange.DeleteIndex, value)
+            return (
+                self._name == other.get_name()
+                and self._if_exists == other.is_if_exists()
+            )
+
+        def __hash__(self) -> int:
+            return hash((self._name, self._if_exists))
+
+    class ColumnPosition(ABC):
+        """The interface for all column positions.
+
+        Column positions are used to specify the position of a column when 
adding
+        a new column to a table.
+        """
+
+        @staticmethod
+        def first() -> "First":
+            """The first position of `ColumnPosition` instance.
+
+            Returns:
+                First:
+                    The first position of `ColumnPosition` instance.
+            """
+            return First()
+
+        @staticmethod
+        def after(column: str) -> "After":
+            """Returns the position after the given column.
+
+            Args:
+                column:
+                    The name of the reference column to place the new column 
after.
+
+            Returns:
+                After:
+                    The position after the given column.
+            """
+            return After(column)
+
+        @staticmethod
+        def default_pos() -> "Default":
+            """Returns the default position of `ColumnPosition` instance.
+
+            Returns:
+                Default:
+                    The default position of `ColumnPosition` instance.
+            """
+            return Default()
+
+    class ColumnChange(ABC):
+        """The interface for all column changes.
+
+        Column changes are used to modify the schema of a table.
+        """
+
+        @abstractmethod
+        def field_name(self) -> list[str]:
+            """Retrieves the field name of the column to be modified.
+
+            Returns:
+                list[str]:
+                    A list of strings representing the field name.
+            """
+
+
+@final
+@dataclass(frozen=True)
+class First(TableChange.ColumnPosition):
+    """Column position FIRST.
+
+    It means the specified column should be the first column. Note that, the 
specified column
+    may be a nested field, and then FIRST means this field should be the first 
one within the
+    struct.
+    """
+
+    def __str__(self):
+        return "FIRST"
+
+
+@final
+@dataclass(frozen=True)
+class After(TableChange.ColumnPosition):
+    """Column position AFTER
+
+    It means the specified column should be put after the given `column`.
+    Note that, the specified column may be a nested field, and then the given 
`column`
+    refers to a field in the same struct.
+    """
+
+    _column: str = field(metadata=config(field_name="column"))
+
+    def get_column(self) -> str:
+        """Retrieves the name of the reference column after which the 
specified column will be placed.
+
+        Returns:
+            str: The name of the reference column.
+        """
+        return self._column
+
+    def __str__(self):
+        return f"AFTER {self._column}"
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, After):
+            return False
+        other = cast(After, value)
+        return self._column == other.get_column()
+
+    def __hash__(self) -> int:
+        return hash(self._column)
+
+
+@final
+@dataclass(frozen=True)
+class Default(TableChange.ColumnPosition):
+    """Column position DEFAULT.
+
+    It means the position of the column was ignored by the user, and should be 
determined
+    by the catalog implementation.
+    """
+
+    def __str__(self):
+        return "DEFAULT"
+
+
+@final
+@dataclass(frozen=True)
+class AddColumn(TableChange.ColumnChange):
+    """A TableChange to add a field.
+
+    The implementation may need to back-fill all the existing data to add this 
new column,
+    or remember the column default value specified here and let the reader 
fill the column value
+    when reading existing data that do not have this new column.
+
+    If the field already exists, the change must result in an 
`IllegalArgumentException`.
+    If the new field is nested and its parent does not exist or is not a 
struct, the change must
+    result in an `IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _data_type: Type = field(metadata=config(field_name="data_type"))
+    _comment: Optional[str] = field(default=None, 
metadata=config(field_name="comment"))
+    _position: Optional[TableChange.ColumnPosition] = field(
+        default=None, metadata=config(field_name="position")
+    )
+    _nullable: bool = field(default=True, 
metadata=config(field_name="nullable"))
+    _auto_increment: bool = field(
+        default=False, metadata=config(field_name="auto_increment")
+    )
+    _default_value: Optional[Expression] = field(
+        default=None,
+        metadata=config(field_name="default_value"),
+    )
+
+    def get_field_name(self) -> list[str]:
+        """Retrieves the field name of the new column.
+
+        Returns:
+            list[str]: The field name of the new column.
+        """
+        return self._field_name
+
+    def get_data_type(self) -> Type:
+        """Retrieves the data type of the new column.
+
+        Returns:
+            Type: The data type of the new column.
+        """
+        return self._data_type
+
+    def get_comment(self) -> Optional[str]:
+        """Retrieves the comment for the new column.
+
+        Returns:
+            str: comment for the new column.
+        """
+        return self._comment
+
+    def get_position(self) -> Optional[TableChange.ColumnPosition]:
+        """Retrieves the position where the new column should be added.
+
+        Returns:
+            TableChange.ColumnPosition:
+                The position of the column.
+        """
+        return self._position
+
+    def is_nullable(self) -> bool:
+        """Checks if the new column is nullable.
+
+        Returns:
+            bool: `True` if the column is nullable; `False` otherwise.
+        """
+        return self._nullable
+
+    def is_auto_increment(self) -> bool:
+        """Checks if the new column is auto-increment.
+
+        Returns:
+            bool: `True` if the column is auto-increment; `False` otherwise.
+        """
+        return self._auto_increment
+
+    def get_default_value(self) -> Expression:
+        """Retrieves the default value of the new column.
+
+        Returns:
+            Expression: The default value of the column.
+        """
+        return (
+            self._default_value if self._default_value else 
Column.DEFAULT_VALUE_NOT_SET
+        )
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, AddColumn):
+            return False
+        other = cast(AddColumn, value)
+        return (
+            self._nullable == other.is_nullable()
+            and self._auto_increment == other.is_auto_increment()
+            and self._field_name == other.get_field_name()
+            and self._comment == other.get_comment()
+            and self._data_type == other.get_data_type()
+            and self._position == other.get_position()
+            and self.get_default_value() == other.get_default_value()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(
+            (
+                self._data_type,
+                self._comment,
+                self._position,
+                self._nullable,
+                self._auto_increment,
+                (
+                    tuple(self._default_value)
+                    if self._default_value == Column.DEFAULT_VALUE_NOT_SET
+                    else self._default_value
+                ),
+            )
+        ) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class RenameColumn(TableChange.ColumnChange):
+    """A `TableChange` to rename a field.
+
+    The name is used to find the field to rename. The new name will replace 
the **leaf field name**.
+    For example, `rename_column("a.b.c", "x")` should produce column **a.b.x**.
+
+    If the field does not exist, the change must result in an 
`IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _new_name: str = field(metadata=config(field_name="new_name"))
+
+    def get_field_name(self) -> list[str]:
+        """Retrieves the hierarchical field name of the column to be renamed.
+
+        Returns:
+            list[str]: The field name of the column to be renamed.
+        """
+        return self._field_name
+
+    def get_new_name(self) -> str:
+        """Retrieves the new name for the column.
+
+        Returns:
+            str: The new name of the column.
+        """
+        return self._new_name
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, RenameColumn):
+            return False
+        other = cast(RenameColumn, value)
+        return (
+            self._field_name == other.get_field_name()
+            and self._new_name == other.get_new_name()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(self._new_name) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class UpdateColumnDefaultValue(TableChange.ColumnChange):
+    """A `TableChange` to update the default value of a field.
+
+    The field names are used to find the field to update.
+
+    If the field does not exist, the change must result in an 
`IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _new_default_value: Optional[Expression] = field(
+        metadata=config(field_name="new_default_value")
+    )
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_new_default_value(self) -> Expression:
+        return (
+            self._new_default_value
+            if self._new_default_value
+            else Column.DEFAULT_VALUE_NOT_SET
+        )
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, UpdateColumnDefaultValue):
+            return False
+        other = cast(UpdateColumnDefaultValue, value)
+        return (
+            self._field_name == other.field_name()
+            and self.get_new_default_value() == other.get_new_default_value()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(
+            tuple(self.get_new_default_value())
+            if self._new_default_value == Column.DEFAULT_VALUE_NOT_SET
+            else self._new_default_value
+        ) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class UpdateColumnType(TableChange.ColumnChange):
+    """A `TableChange` to update the type of a field.
+
+    The field names are used to find the field to update.
+
+    If the field does not exist, the change must result in an 
`IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _new_data_type: Type = field(metadata=config(field_name="new_data_type"))
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_new_data_type(self) -> Type:
+        return self._new_data_type
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, UpdateColumnType):
+            return False
+        other = cast(UpdateColumnType, value)
+        return (
+            self._field_name == other.get_field_name()
+            and self._new_data_type == other.get_new_data_type()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(self._new_data_type) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class UpdateColumnComment(TableChange.ColumnChange):
+    """A `TableChange` to update the comment of a field.
+
+    The field names are used to find the field to update.
+
+    If the field does not exist, the change must result in an 
`IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _new_comment: str = field(metadata=config(field_name="new_comment"))
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_new_comment(self) -> str:
+        return self._new_comment
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, UpdateColumnComment):
+            return False
+        other = cast(UpdateColumnComment, value)
+        return (
+            self._field_name == other.get_field_name()
+            and self._new_comment == other.get_new_comment()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(self._new_comment) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class UpdateColumnPosition(TableChange.ColumnChange):
+    """A TableChange to update the position of a field.
+
+    The field names are used to find the field to update.
+
+    If the field does not exist, the change must result in an 
`IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _position: TableChange.ColumnPosition = field(
+        metadata=config(field_name="position")
+    )
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_field_name(self) -> list[str]:
+        """Retrieves the field name of the column whose position is being 
updated.
+
+        Returns:
+            list[str]: A list of strings representing the field name.
+        """
+        return self._field_name
+
+    def get_position(self) -> TableChange.ColumnPosition:
+        """Retrieves the new position for the column.
+
+        Returns:
+            TableChange.ColumnPosition: The new position of the column.
+        """
+        return self._position
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, UpdateColumnPosition):
+            return False
+        other = cast(UpdateColumnPosition, value)
+        return (
+            self._field_name == other.get_field_name()
+            and self._position == other.get_position()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(self._position) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class DeleteColumn(TableChange.ColumnChange):
+    """A TableChange to delete a field.
+
+    If the field does not exist, the change must result in an 
`IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _if_exists: bool = field(metadata=config(field_name="if_exists"))
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_field_name(self) -> list[str]:
+        """Retrieves the field name of the column to be deleted.
+
+        Returns:
+            list[str]: A list of strings representing the field name.
+        """
+        return self._field_name
+
+    def get_if_exists(self) -> bool:
+        """Checks if the field should be deleted only if it exists.
+
+        Returns:
+            bool: `True` if the field should be deleted only if it exists; 
`False` otherwise.
+        """
+        return self._if_exists
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, DeleteColumn):
+            return False
+        other = cast(DeleteColumn, value)
+        return (
+            self._field_name == other.get_field_name()
+            and self._if_exists == other.get_if_exists()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(self._if_exists) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class UpdateColumnNullability(TableChange.ColumnChange):
+    """A TableChange to update the nullability of a field.
+
+    The field names are used to find the field to update.
+
+    If the field does not exist, the change must result in an 
`IllegalArgumentException`.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _nullable: bool = field(metadata=config(field_name="nullable"))
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def get_field_name(self) -> list[str]:
+        """Retrieves the field name of the column whose nullability is being 
updated.
+
+        Returns:
+            list[str]: A list of strings representing the field name.
+        """
+        return self._field_name
+
+    def get_nullable(self) -> bool:
+        """Checks if the column is nullable.
+
+        Returns:
+            bool: `True` if the column is nullable; `False` otherwise.
+        """
+        return self._nullable
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, UpdateColumnNullability):
+            return False
+        other = cast(UpdateColumnNullability, value)
+        return (
+            self._field_name == other.get_field_name()
+            and self._nullable == other.get_nullable()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(self._nullable) + hash(tuple(self._field_name))
+
+
+@final
+@dataclass(frozen=True)
+class UpdateColumnAutoIncrement(TableChange.ColumnChange):
+    """A TableChange to update the `autoIncrement` of a field.
+
+    True is to add autoIncrement, false is to delete autoIncrement.
+    """
+
+    _field_name: list[str] = field(metadata=config(field_name="field_name"))
+    _auto_increment: bool = field(metadata=config(field_name="auto_increment"))
+
+    def field_name(self) -> list[str]:
+        return self._field_name
+
+    def is_auto_increment(self) -> bool:
+        """Checks if the column is `autoIncrement`.
+
+        Returns:
+            bool: `True` if the column is `autoIncrement`; `False` otherwise.
+        """
+        return self._auto_increment
+
+    def __eq__(self, value: object) -> bool:
+        if not isinstance(value, UpdateColumnAutoIncrement):
+            return False
+        other = cast(UpdateColumnAutoIncrement, value)
+        return (
+            self._field_name == other.field_name()
+            and self._auto_increment == other.is_auto_increment()
+        )
+
+    def __hash__(self) -> int:
+        return 31 * hash(self._auto_increment) + hash(tuple(self._field_name))
diff --git 
a/clients/client-python/gravitino/api/expressions/indexes/__init__.py 
b/clients/client-python/gravitino/api/rel/types/__init__.py
similarity index 100%
rename from clients/client-python/gravitino/api/expressions/indexes/__init__.py
rename to clients/client-python/gravitino/api/rel/types/__init__.py
diff --git a/clients/client-python/gravitino/api/types/json_serdes/__init__.py 
b/clients/client-python/gravitino/api/rel/types/json_serdes/__init__.py
similarity index 85%
rename from clients/client-python/gravitino/api/types/json_serdes/__init__.py
rename to clients/client-python/gravitino/api/rel/types/json_serdes/__init__.py
index fb9e11264c..aa841f09a7 100644
--- a/clients/client-python/gravitino/api/types/json_serdes/__init__.py
+++ b/clients/client-python/gravitino/api/rel/types/json_serdes/__init__.py
@@ -15,7 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-from gravitino.api.types.json_serdes.base import JsonSerializable
-from gravitino.api.types.json_serdes.type_serdes import TypeSerdes
+from gravitino.api.rel.types.json_serdes.base import JsonSerializable
+from gravitino.api.rel.types.json_serdes.type_serdes import TypeSerdes
 
 __all__ = ["JsonSerializable", "TypeSerdes"]
diff --git 
a/clients/client-python/gravitino/api/types/json_serdes/_helper/serdes_utils.py 
b/clients/client-python/gravitino/api/rel/types/json_serdes/_helper/serdes_utils.py
similarity index 99%
rename from 
clients/client-python/gravitino/api/types/json_serdes/_helper/serdes_utils.py
rename to 
clients/client-python/gravitino/api/rel/types/json_serdes/_helper/serdes_utils.py
index cab0ebbacc..25656fa0e4 100644
--- 
a/clients/client-python/gravitino/api/types/json_serdes/_helper/serdes_utils.py
+++ 
b/clients/client-python/gravitino/api/rel/types/json_serdes/_helper/serdes_utils.py
@@ -20,8 +20,8 @@ from typing import Any, Dict, Union, overload
 
 from dataclasses_json.core import Json
 
-from gravitino.api.types.type import Name, Type
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.type import Name, Type
+from gravitino.api.rel.types.types import Types
 from gravitino.utils.precondition import Precondition
 from gravitino.utils.serdes import SerdesUtilsBase
 
diff --git a/clients/client-python/gravitino/api/types/json_serdes/base.py 
b/clients/client-python/gravitino/api/rel/types/json_serdes/base.py
similarity index 95%
rename from clients/client-python/gravitino/api/types/json_serdes/base.py
rename to clients/client-python/gravitino/api/rel/types/json_serdes/base.py
index 9f44de8462..aa20d76ba7 100644
--- a/clients/client-python/gravitino/api/types/json_serdes/base.py
+++ b/clients/client-python/gravitino/api/rel/types/json_serdes/base.py
@@ -22,9 +22,9 @@ from dataclasses_json.core import Json
 
 from gravitino.api.expressions.distributions.distribution import Distribution
 from gravitino.api.expressions.expression import Expression
-from gravitino.api.expressions.indexes.index import Index
 from gravitino.api.expressions.sorts.sort_order import SortOrder
-from gravitino.api.types.types import Type
+from gravitino.api.rel.indexes.index import Index
+from gravitino.api.rel.types.types import Type
 from gravitino.dto.rel.partitioning.partitioning import Partitioning
 from gravitino.dto.rel.partitions.partition_dto import PartitionDTO
 
diff --git 
a/clients/client-python/gravitino/api/types/json_serdes/type_serdes.py 
b/clients/client-python/gravitino/api/rel/types/json_serdes/type_serdes.py
similarity index 89%
rename from clients/client-python/gravitino/api/types/json_serdes/type_serdes.py
rename to 
clients/client-python/gravitino/api/rel/types/json_serdes/type_serdes.py
index 4577987486..8e97c54b75 100644
--- a/clients/client-python/gravitino/api/types/json_serdes/type_serdes.py
+++ b/clients/client-python/gravitino/api/rel/types/json_serdes/type_serdes.py
@@ -18,9 +18,9 @@
 
 from dataclasses_json.core import Json
 
-from gravitino.api.types.json_serdes._helper.serdes_utils import SerdesUtils
-from gravitino.api.types.json_serdes.base import JsonSerializable
-from gravitino.api.types.type import Type
+from gravitino.api.rel.types.json_serdes._helper.serdes_utils import 
SerdesUtils
+from gravitino.api.rel.types.json_serdes.base import JsonSerializable
+from gravitino.api.rel.types.type import Type
 
 
 class TypeSerdes(JsonSerializable[Type]):
diff --git a/clients/client-python/gravitino/api/types/type.py 
b/clients/client-python/gravitino/api/rel/types/type.py
similarity index 100%
rename from clients/client-python/gravitino/api/types/type.py
rename to clients/client-python/gravitino/api/rel/types/type.py
diff --git a/clients/client-python/gravitino/api/types/types.py 
b/clients/client-python/gravitino/api/rel/types/types.py
similarity index 99%
rename from clients/client-python/gravitino/api/types/types.py
rename to clients/client-python/gravitino/api/rel/types/types.py
index 9549c1ec5e..f182f086bf 100644
--- a/clients/client-python/gravitino/api/types/types.py
+++ b/clients/client-python/gravitino/api/rel/types/types.py
@@ -19,7 +19,7 @@ from __future__ import annotations
 
 from typing import List
 
-from .type import (
+from gravitino.api.rel.types.type import (
     ComplexType,
     DateTimeType,
     FractionType,
diff --git a/clients/client-python/gravitino/dto/rel/column_dto.py 
b/clients/client-python/gravitino/dto/rel/column_dto.py
index 42286fd128..fba28be2ec 100644
--- a/clients/client-python/gravitino/dto/rel/column_dto.py
+++ b/clients/client-python/gravitino/dto/rel/column_dto.py
@@ -22,11 +22,11 @@ from typing import List, Optional, Union, cast
 
 from dataclasses_json import DataClassJsonMixin, config
 
-from gravitino.api.column import Column
 from gravitino.api.expressions.expression import Expression
-from gravitino.api.types.json_serdes.type_serdes import TypeSerdes
-from gravitino.api.types.type import Type
-from gravitino.api.types.types import Types
+from gravitino.api.rel.column import Column
+from gravitino.api.rel.types.json_serdes.type_serdes import TypeSerdes
+from gravitino.api.rel.types.type import Type
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.json_serdes.column_default_value_serdes 
import (
     ColumnDefaultValueSerdes,
 )
diff --git 
a/clients/client-python/gravitino/dto/rel/expressions/json_serdes/_helper/serdes_utils.py
 
b/clients/client-python/gravitino/dto/rel/expressions/json_serdes/_helper/serdes_utils.py
index 4edfc133f7..53a9ec9b63 100644
--- 
a/clients/client-python/gravitino/dto/rel/expressions/json_serdes/_helper/serdes_utils.py
+++ 
b/clients/client-python/gravitino/dto/rel/expressions/json_serdes/_helper/serdes_utils.py
@@ -17,7 +17,7 @@
 
 from typing import Any, Dict, cast
 
-from gravitino.api.types.json_serdes._helper.serdes_utils import (
+from gravitino.api.rel.types.json_serdes._helper.serdes_utils import (
     SerdesUtils as TypesSerdesUtils,
 )
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
diff --git 
a/clients/client-python/gravitino/dto/rel/expressions/json_serdes/column_default_value_serdes.py
 
b/clients/client-python/gravitino/dto/rel/expressions/json_serdes/column_default_value_serdes.py
index 6e717c4f62..2b6905619b 100644
--- 
a/clients/client-python/gravitino/dto/rel/expressions/json_serdes/column_default_value_serdes.py
+++ 
b/clients/client-python/gravitino/dto/rel/expressions/json_serdes/column_default_value_serdes.py
@@ -19,9 +19,9 @@ from typing import overload
 
 from dataclasses_json.core import Json
 
-from gravitino.api.column import Column
 from gravitino.api.expressions.expression import Expression
-from gravitino.api.types.json_serdes.base import JsonSerializable
+from gravitino.api.rel.column import Column
+from gravitino.api.rel.types.json_serdes.base import JsonSerializable
 from gravitino.dto.rel.expressions.json_serdes._helper.serdes_utils import 
SerdesUtils
 
 
diff --git a/clients/client-python/gravitino/dto/rel/expressions/literal_dto.py 
b/clients/client-python/gravitino/dto/rel/expressions/literal_dto.py
index 7e416c31c3..31dc1cc2cc 100644
--- a/clients/client-python/gravitino/dto/rel/expressions/literal_dto.py
+++ b/clients/client-python/gravitino/dto/rel/expressions/literal_dto.py
@@ -20,11 +20,11 @@ from __future__ import annotations
 from typing import TYPE_CHECKING, ClassVar
 
 from gravitino.api.expressions.literals.literal import Literal
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.function_arg import FunctionArg
 
 if TYPE_CHECKING:
-    from gravitino.api.types.type import Type
+    from gravitino.api.rel.types.type import Type
 
 
 class LiteralDTO(Literal[str], FunctionArg):
diff --git a/clients/client-python/gravitino/dto/rel/indexes/index_dto.py 
b/clients/client-python/gravitino/dto/rel/indexes/index_dto.py
index 539c660bf5..b804fc0cc0 100644
--- a/clients/client-python/gravitino/dto/rel/indexes/index_dto.py
+++ b/clients/client-python/gravitino/dto/rel/indexes/index_dto.py
@@ -18,7 +18,7 @@
 from functools import reduce
 from typing import ClassVar, List, Optional
 
-from gravitino.api.expressions.indexes.index import Index
+from gravitino.api.rel.indexes.index import Index
 from gravitino.utils.precondition import Precondition
 
 
diff --git 
a/clients/client-python/gravitino/dto/rel/indexes/json_serdes/index_serdes.py 
b/clients/client-python/gravitino/dto/rel/indexes/json_serdes/index_serdes.py
index a1eab07e15..5aa004eb21 100644
--- 
a/clients/client-python/gravitino/dto/rel/indexes/json_serdes/index_serdes.py
+++ 
b/clients/client-python/gravitino/dto/rel/indexes/json_serdes/index_serdes.py
@@ -17,8 +17,8 @@
 
 from typing import Any
 
-from gravitino.api.expressions.indexes.index import Index
-from gravitino.api.types.json_serdes import JsonSerializable
+from gravitino.api.rel.indexes.index import Index
+from gravitino.api.rel.types.json_serdes import JsonSerializable
 from gravitino.dto.rel.indexes.index_dto import IndexDTO
 from gravitino.utils.precondition import Precondition
 from gravitino.utils.serdes import SerdesUtilsBase
diff --git 
a/clients/client-python/gravitino/dto/rel/json_serdes/distribution_serdes.py 
b/clients/client-python/gravitino/dto/rel/json_serdes/distribution_serdes.py
index 84e88642b9..eb0993a433 100644
--- a/clients/client-python/gravitino/dto/rel/json_serdes/distribution_serdes.py
+++ b/clients/client-python/gravitino/dto/rel/json_serdes/distribution_serdes.py
@@ -19,7 +19,7 @@
 from typing import Any
 
 from gravitino.api.expressions.distributions.strategy import Strategy
-from gravitino.api.types.json_serdes.base import JsonSerializable
+from gravitino.api.rel.types.json_serdes.base import JsonSerializable
 from gravitino.dto.rel.distribution_dto import DistributionDTO
 from gravitino.dto.rel.expressions.json_serdes._helper.serdes_utils import 
SerdesUtils
 from gravitino.utils.precondition import Precondition
diff --git 
a/clients/client-python/gravitino/dto/rel/json_serdes/sort_order_serdes.py 
b/clients/client-python/gravitino/dto/rel/json_serdes/sort_order_serdes.py
index 7eadb5416f..bd457066aa 100644
--- a/clients/client-python/gravitino/dto/rel/json_serdes/sort_order_serdes.py
+++ b/clients/client-python/gravitino/dto/rel/json_serdes/sort_order_serdes.py
@@ -19,7 +19,7 @@ from typing import Any, Dict
 
 from gravitino.api.expressions.sorts.null_ordering import NullOrdering
 from gravitino.api.expressions.sorts.sort_direction import SortDirection
-from gravitino.api.types.json_serdes import JsonSerializable
+from gravitino.api.rel.types.json_serdes import JsonSerializable
 from gravitino.dto.rel.expressions.json_serdes._helper.serdes_utils import 
SerdesUtils
 from gravitino.dto.rel.sort_order_dto import SortOrderDTO
 from gravitino.utils.precondition import Precondition
diff --git 
a/clients/client-python/gravitino/dto/rel/partitioning/json_serdes/partitioning_serdes.py
 
b/clients/client-python/gravitino/dto/rel/partitioning/json_serdes/partitioning_serdes.py
index 2b3a9bbc03..da9e95701b 100644
--- 
a/clients/client-python/gravitino/dto/rel/partitioning/json_serdes/partitioning_serdes.py
+++ 
b/clients/client-python/gravitino/dto/rel/partitioning/json_serdes/partitioning_serdes.py
@@ -19,7 +19,7 @@ from contextlib import suppress
 from types import MappingProxyType
 from typing import Any, Dict, Final, cast
 
-from gravitino.api.types.json_serdes.base import JsonSerializable
+from gravitino.api.rel.types.json_serdes.base import JsonSerializable
 from gravitino.dto.rel.expressions.json_serdes._helper.serdes_utils import (
     SerdesUtils as ExpressionSerdesUtils,
 )
diff --git 
a/clients/client-python/gravitino/dto/rel/partitions/identity_partition_dto.py 
b/clients/client-python/gravitino/dto/rel/partitions/identity_partition_dto.py
index ab675cd769..e4d26eb0cb 100644
--- 
a/clients/client-python/gravitino/dto/rel/partitions/identity_partition_dto.py
+++ 
b/clients/client-python/gravitino/dto/rel/partitions/identity_partition_dto.py
@@ -18,7 +18,7 @@
 
 from typing import Dict, List
 
-from gravitino.api.expressions.partitions.identity_partition import 
IdentityPartition
+from gravitino.api.rel.partitions.identity_partition import IdentityPartition
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 from gravitino.dto.rel.partitions.partition_dto import PartitionDTO
 
diff --git 
a/clients/client-python/gravitino/dto/rel/partitions/json_serdes/partition_dto_serdes.py
 
b/clients/client-python/gravitino/dto/rel/partitions/json_serdes/partition_dto_serdes.py
index 383ba8d3a9..030ffb5e24 100644
--- 
a/clients/client-python/gravitino/dto/rel/partitions/json_serdes/partition_dto_serdes.py
+++ 
b/clients/client-python/gravitino/dto/rel/partitions/json_serdes/partition_dto_serdes.py
@@ -17,7 +17,7 @@
 
 from typing import Any, Dict
 
-from gravitino.api.types.json_serdes.base import JsonSerializable
+from gravitino.api.rel.types.json_serdes.base import JsonSerializable
 from gravitino.dto.rel.partitions.json_serdes._helper.serdes_utils import 
SerdesUtils
 from gravitino.dto.rel.partitions.partition_dto import PartitionDTO
 
diff --git 
a/clients/client-python/gravitino/dto/rel/partitions/list_partition_dto.py 
b/clients/client-python/gravitino/dto/rel/partitions/list_partition_dto.py
index 9ccd542b75..ec08f658a9 100644
--- a/clients/client-python/gravitino/dto/rel/partitions/list_partition_dto.py
+++ b/clients/client-python/gravitino/dto/rel/partitions/list_partition_dto.py
@@ -18,7 +18,7 @@
 
 from typing import Dict, List
 
-from gravitino.api.expressions.partitions.list_partition import ListPartition
+from gravitino.api.rel.partitions.list_partition import ListPartition
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 from gravitino.dto.rel.partitions.partition_dto import PartitionDTO
 
diff --git 
a/clients/client-python/gravitino/dto/rel/partitions/partition_dto.py 
b/clients/client-python/gravitino/dto/rel/partitions/partition_dto.py
index 40047d6901..ed68931da7 100644
--- a/clients/client-python/gravitino/dto/rel/partitions/partition_dto.py
+++ b/clients/client-python/gravitino/dto/rel/partitions/partition_dto.py
@@ -19,7 +19,7 @@
 from abc import abstractmethod
 from enum import Enum, unique
 
-from gravitino.api.expressions.partitions.partition import Partition
+from gravitino.api.rel.partitions.partition import Partition
 
 
 class PartitionDTO(Partition):
diff --git 
a/clients/client-python/gravitino/dto/rel/partitions/range_partition_dto.py 
b/clients/client-python/gravitino/dto/rel/partitions/range_partition_dto.py
index 967ff1a2b7..73930c9843 100644
--- a/clients/client-python/gravitino/dto/rel/partitions/range_partition_dto.py
+++ b/clients/client-python/gravitino/dto/rel/partitions/range_partition_dto.py
@@ -18,7 +18,7 @@
 
 from typing import Dict
 
-from gravitino.api.expressions.partitions.range_partition import RangePartition
+from gravitino.api.rel.partitions.range_partition import RangePartition
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 from gravitino.dto.rel.partitions.partition_dto import PartitionDTO
 
diff --git a/clients/client-python/gravitino/utils/serdes.py 
b/clients/client-python/gravitino/utils/serdes.py
index b862ba9259..ddee51ac84 100644
--- a/clients/client-python/gravitino/utils/serdes.py
+++ b/clients/client-python/gravitino/utils/serdes.py
@@ -20,7 +20,7 @@ from collections.abc import Mapping
 from types import MappingProxyType
 from typing import Final, Pattern, Set
 
-from gravitino.api.types.types import Name, Types
+from gravitino.api.rel.types.types import Name, Types
 
 
 class SerdesUtilsBase:
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_column_default_value_serdes.py
 
b/clients/client-python/tests/unittests/dto/rel/test_column_default_value_serdes.py
index 303dd98f45..999ec0f476 100644
--- 
a/clients/client-python/tests/unittests/dto/rel/test_column_default_value_serdes.py
+++ 
b/clients/client-python/tests/unittests/dto/rel/test_column_default_value_serdes.py
@@ -18,8 +18,8 @@
 import unittest
 from unittest.mock import patch
 
-from gravitino.api.column import Column
-from gravitino.api.types.types import Types
+from gravitino.api.rel.column import Column
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.func_expression_dto import FuncExpressionDTO
 from gravitino.dto.rel.expressions.json_serdes._helper.serdes_utils import 
SerdesUtils
diff --git a/clients/client-python/tests/unittests/dto/rel/test_column_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_column_dto.py
index 0a9c2b4a3d..30b4159b37 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_column_dto.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_column_dto.py
@@ -19,10 +19,10 @@ import json
 import unittest
 from itertools import product
 
-from gravitino.api.column import Column
-from gravitino.api.types.json_serdes import TypeSerdes
-from gravitino.api.types.json_serdes._helper.serdes_utils import SerdesUtils
-from gravitino.api.types.types import Types
+from gravitino.api.rel.column import Column
+from gravitino.api.rel.types.json_serdes import TypeSerdes
+from gravitino.api.rel.types.json_serdes._helper.serdes_utils import 
SerdesUtils
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.func_expression_dto import FuncExpressionDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_distribution_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_distribution_dto.py
index fe88afa0e1..07a2443bfb 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_distribution_dto.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_distribution_dto.py
@@ -18,7 +18,7 @@
 import unittest
 
 from gravitino.api.expressions.distributions.strategy import Strategy
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.distribution_dto import DistributionDTO
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_field_reference_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_field_reference_dto.py
index 230a88ad67..78b1ca9d85 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_field_reference_dto.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_field_reference_dto.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.function_arg import FunctionArg
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_func_expression_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_func_expression_dto.py
index c29324b260..b78cfd5ef4 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_func_expression_dto.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_func_expression_dto.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.func_expression_dto import FuncExpressionDTO
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
diff --git a/clients/client-python/tests/unittests/dto/rel/test_function_arg.py 
b/clients/client-python/tests/unittests/dto/rel/test_function_arg.py
index 83ebb413b5..9aa4176c08 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_function_arg.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_function_arg.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.func_expression_dto import FuncExpressionDTO
diff --git a/clients/client-python/tests/unittests/dto/rel/test_index_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_index_dto.py
index 5bb9fa31dc..9a17862a23 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_index_dto.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_index_dto.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.expressions.indexes.index import Index
+from gravitino.api.rel.indexes.index import Index
 from gravitino.dto.rel.indexes.index_dto import IndexDTO
 from gravitino.exceptions.base import IllegalArgumentException
 
diff --git a/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py 
b/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py
index 5373193209..790f102a24 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py
@@ -21,7 +21,7 @@ from dataclasses import dataclass, field
 
 from dataclasses_json import DataClassJsonMixin, config
 
-from gravitino.api.expressions.indexes.index import Index
+from gravitino.api.rel.indexes.index import Index
 from gravitino.dto.rel.indexes.index_dto import IndexDTO
 from gravitino.dto.rel.indexes.json_serdes.index_serdes import IndexSerdes
 from gravitino.exceptions.base import IllegalArgumentException
diff --git a/clients/client-python/tests/unittests/dto/rel/test_literal_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_literal_dto.py
index d730a49de2..a3a69084cc 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_literal_dto.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_literal_dto.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 
 
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_non_single_field_partitioning_dto.py
 
b/clients/client-python/tests/unittests/dto/rel/test_non_single_field_partitioning_dto.py
index 633d34d3db..ca50c68c2a 100644
--- 
a/clients/client-python/tests/unittests/dto/rel/test_non_single_field_partitioning_dto.py
+++ 
b/clients/client-python/tests/unittests/dto/rel/test_non_single_field_partitioning_dto.py
@@ -21,7 +21,7 @@ from itertools import chain
 
 from gravitino.api.expressions.literals.literals import Literals
 from gravitino.api.expressions.named_reference import NamedReference
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 from gravitino.dto.rel.partitioning.bucket_partitioning_dto import 
BucketPartitioningDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_partition_dto_serdes.py 
b/clients/client-python/tests/unittests/dto/rel/test_partition_dto_serdes.py
index 464c705ad8..e83df8b051 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_partition_dto_serdes.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_partition_dto_serdes.py
@@ -21,7 +21,7 @@ from enum import Enum
 from typing import cast
 from unittest.mock import patch
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.json_serdes._helper.serdes_utils import (
     SerdesUtils as ExpressionSerdesUtils,
 )
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_partition_dtos.py 
b/clients/client-python/tests/unittests/dto/rel/test_partition_dtos.py
index 9bcf9f106a..ed0f083c11 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_partition_dtos.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_partition_dtos.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 from gravitino.dto.rel.partitions.identity_partition_dto import 
IdentityPartitionDTO
 from gravitino.dto.rel.partitions.list_partition_dto import ListPartitionDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_partition_utils.py 
b/clients/client-python/tests/unittests/dto/rel/test_partition_utils.py
index 8d69c127a1..ac639f45ed 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_partition_utils.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_partition_utils.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.partition_utils import PartitionUtils
 from gravitino.exceptions.base import IllegalArgumentException
diff --git a/clients/client-python/tests/unittests/dto/rel/test_partitioning.py 
b/clients/client-python/tests/unittests/dto/rel/test_partitioning.py
index 370bdec0b1..475d4a937a 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_partitioning.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_partitioning.py
@@ -18,7 +18,7 @@
 import unittest
 
 from gravitino.api.expressions.named_reference import NamedReference
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.partitioning.partitioning import (
     Partitioning,
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_partitioning_serdes.py 
b/clients/client-python/tests/unittests/dto/rel/test_partitioning_serdes.py
index ee99c7112f..4eccd68234 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_partitioning_serdes.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_partitioning_serdes.py
@@ -20,7 +20,7 @@ import unittest
 from enum import Enum
 from unittest.mock import patch
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 from gravitino.dto.rel.partitioning.day_partitioning_dto import 
DayPartitioningDTO
diff --git a/clients/client-python/tests/unittests/dto/rel/test_serdes_utils.py 
b/clients/client-python/tests/unittests/dto/rel/test_serdes_utils.py
index d8b29659f6..0151f759cb 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_serdes_utils.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_serdes_utils.py
@@ -19,7 +19,7 @@ import unittest
 from enum import Enum
 from unittest.mock import patch
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.func_expression_dto import FuncExpressionDTO
 from gravitino.dto.rel.expressions.function_arg import FunctionArg
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_single_field_partitioning_dto.py
 
b/clients/client-python/tests/unittests/dto/rel/test_single_field_partitioning_dto.py
index 1efb860541..37991825b8 100644
--- 
a/clients/client-python/tests/unittests/dto/rel/test_single_field_partitioning_dto.py
+++ 
b/clients/client-python/tests/unittests/dto/rel/test_single_field_partitioning_dto.py
@@ -18,7 +18,7 @@
 import unittest
 
 from gravitino.api.expressions.named_reference import NamedReference
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.partitioning.day_partitioning_dto import 
DayPartitioningDTO
 from gravitino.dto.rel.partitioning.hour_partitioning_dto import 
HourPartitioningDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_sort_order_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_sort_order_dto.py
index 68a39e1ab3..10e32d619c 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_sort_order_dto.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_sort_order_dto.py
@@ -19,7 +19,7 @@ import unittest
 
 from gravitino.api.expressions.sorts.null_ordering import NullOrdering
 from gravitino.api.expressions.sorts.sort_direction import SortDirection
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.column_dto import ColumnDTO
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.sort_order_dto import SortOrderDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_sort_order_serdes.py 
b/clients/client-python/tests/unittests/dto/rel/test_sort_order_serdes.py
index bb2bce511a..af850388f8 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_sort_order_serdes.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_sort_order_serdes.py
@@ -25,7 +25,7 @@ from dataclasses_json import DataClassJsonMixin, config
 
 from gravitino.api.expressions.sorts.null_ordering import NullOrdering
 from gravitino.api.expressions.sorts.sort_direction import SortDirection
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.field_reference_dto import FieldReferenceDTO
 from gravitino.dto.rel.expressions.func_expression_dto import FuncExpressionDTO
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
diff --git 
a/clients/client-python/tests/unittests/dto/rel/test_unparsed_expression_dto.py 
b/clients/client-python/tests/unittests/dto/rel/test_unparsed_expression_dto.py
index fdae2a9b64..85f034a242 100644
--- 
a/clients/client-python/tests/unittests/dto/rel/test_unparsed_expression_dto.py
+++ 
b/clients/client-python/tests/unittests/dto/rel/test_unparsed_expression_dto.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
 from gravitino.dto.rel.expressions.unparsed_expression_dto import 
UnparsedExpressionDTO
 
diff --git 
a/clients/client-python/tests/unittests/json_serdes/test_type_serdes.py 
b/clients/client-python/tests/unittests/json_serdes/test_type_serdes.py
index 7ef195b649..10f5f77eac 100644
--- a/clients/client-python/tests/unittests/json_serdes/test_type_serdes.py
+++ b/clients/client-python/tests/unittests/json_serdes/test_type_serdes.py
@@ -19,10 +19,10 @@ import random
 import unittest
 from itertools import combinations, product
 
-from gravitino.api.types.json_serdes import TypeSerdes
-from gravitino.api.types.json_serdes._helper.serdes_utils import SerdesUtils
-from gravitino.api.types.type import PrimitiveType
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.json_serdes import TypeSerdes
+from gravitino.api.rel.types.json_serdes._helper.serdes_utils import 
SerdesUtils
+from gravitino.api.rel.types.type import PrimitiveType
+from gravitino.api.rel.types.types import Types
 from gravitino.exceptions.base import IllegalArgumentException
 
 
diff --git a/clients/client-python/tests/unittests/rel/test_indexes.py 
b/clients/client-python/tests/unittests/rel/test_indexes.py
index 6633d07efe..4f9a65f770 100644
--- a/clients/client-python/tests/unittests/rel/test_indexes.py
+++ b/clients/client-python/tests/unittests/rel/test_indexes.py
@@ -17,8 +17,8 @@
 
 import unittest
 
-from gravitino.api.expressions.indexes.index import Index
-from gravitino.api.expressions.indexes.indexes import Indexes
+from gravitino.api.rel.indexes.index import Index
+from gravitino.api.rel.indexes.indexes import Indexes
 
 
 class TestIndexes(unittest.TestCase):
diff --git a/clients/client-python/tests/unittests/rel/test_literals.py 
b/clients/client-python/tests/unittests/rel/test_literals.py
index d9c96b7bab..577b19f297 100644
--- a/clients/client-python/tests/unittests/rel/test_literals.py
+++ b/clients/client-python/tests/unittests/rel/test_literals.py
@@ -15,11 +15,11 @@
 # specific language governing permissions and limitations
 # under the License.
 import unittest
-from datetime import date, time, datetime
+from datetime import date, datetime, time
 from decimal import Decimal
 
 from gravitino.api.expressions.literals.literals import Literals
-from gravitino.api.types.types import Types
+from gravitino.api.rel.types.types import Types
 
 
 class TestLiterals(unittest.TestCase):
diff --git a/clients/client-python/tests/unittests/rel/test_partitions.py 
b/clients/client-python/tests/unittests/rel/test_partitions.py
index a14eb079d6..b6fc768481 100644
--- a/clients/client-python/tests/unittests/rel/test_partitions.py
+++ b/clients/client-python/tests/unittests/rel/test_partitions.py
@@ -18,7 +18,7 @@ import unittest
 from datetime import date
 
 from gravitino.api.expressions.literals.literals import Literals
-from gravitino.api.expressions.partitions.partitions import Partitions
+from gravitino.api.rel.partitions.partitions import Partitions
 
 
 class TestPartitions(unittest.TestCase):
diff --git a/clients/client-python/tests/unittests/rel/test_table_change.py 
b/clients/client-python/tests/unittests/rel/test_table_change.py
new file mode 100644
index 0000000000..c164075684
--- /dev/null
+++ b/clients/client-python/tests/unittests/rel/test_table_change.py
@@ -0,0 +1,566 @@
+# 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.
+
+import unittest
+
+from gravitino.api.expressions.literals.literals import Literals
+from gravitino.api.rel.column import Column
+from gravitino.api.rel.indexes.index import Index
+from gravitino.api.rel.table_change import TableChange
+from gravitino.api.rel.types.types import Types
+
+
+class TestTableChange(unittest.TestCase):
+    def test_table_change_rename(self):
+        rename1, rename2 = (
+            TableChange.rename(f"New table name {i + 1}") for i in range(2)
+        )
+        rename3 = TableChange.rename("New table name 1")
+        self.assertEqual(rename1.get_new_name(), "New table name 1")
+        self.assertEqual(str(rename1), f"RENAMETABLE {rename1.get_new_name()}")
+        self.assertFalse(rename1 == rename2)
+        self.assertFalse(rename1 == "invalid_rename")
+        self.assertTrue(rename1 == rename3)
+        self.assertTrue(hash(rename1) == hash(rename3))
+        self.assertFalse(hash(rename1) == hash(rename2))
+
+    def test_table_change_update_comment(self):
+        new_comment1, new_comment2 = (
+            TableChange.update_comment(f"New comment {i + 1}") for i in 
range(2)
+        )
+        new_comment3 = TableChange.update_comment("New comment 1")
+        self.assertEqual(new_comment1.get_new_comment(), "New comment 1")
+        self.assertEqual(
+            str(new_comment1), f"UPDATECOMMENT 
{new_comment1.get_new_comment()}"
+        )
+        self.assertFalse(new_comment1 == new_comment2)
+        self.assertFalse(new_comment1 == "invalid_update_comment")
+        self.assertTrue(new_comment1 == new_comment3)
+        self.assertTrue(hash(new_comment1) == hash(new_comment3))
+        self.assertFalse(hash(new_comment1) == hash(new_comment2))
+
+    def test_table_change_set_property(self):
+        new_property1, new_property2 = (
+            TableChange.set_property(f"new_property_{i + 1}", str(i + 1))
+            for i in range(2)
+        )
+        new_property3 = TableChange.set_property("new_property_1", "1")
+        self.assertEqual(new_property1.get_property(), "new_property_1")
+        self.assertEqual(new_property1.get_value(), "1")
+        self.assertEqual(
+            str(new_property1),
+            f"SETPROPERTY {new_property1.get_property()} 
{new_property1.get_value()}",
+        )
+        self.assertFalse(new_property1 == new_property2)
+        self.assertFalse(new_property1 == "invalid_set_property")
+        self.assertTrue(new_property1 == new_property3)
+        self.assertTrue(hash(new_property1) == hash(new_property3))
+        self.assertFalse(hash(new_property1) == hash(new_property2))
+
+    def test_table_change_remove_property(self):
+        property1, property2 = (
+            TableChange.remove_property(f"property_{i + 1}") for i in range(2)
+        )
+        property3 = TableChange.remove_property("property_1")
+        self.assertEqual(property1.get_property(), "property_1")
+        self.assertEqual(str(property1), f"REMOVEPROPERTY 
{property1.get_property()}")
+        self.assertFalse(property1 == property2)
+        self.assertFalse(property1 == "invalid_remove_property")
+        self.assertTrue(property1 == property3)
+        self.assertTrue(hash(property1) == hash(property3))
+        self.assertFalse(hash(property1) == hash(property2))
+
+    def test_column_position(self):
+        first = TableChange.ColumnPosition.first()
+        after = TableChange.ColumnPosition.after("column")
+        default_pos = TableChange.ColumnPosition.default_pos()
+
+        self.assertIsInstance(first, TableChange.ColumnPosition)
+        self.assertIsInstance(after, TableChange.ColumnPosition)
+        self.assertIsInstance(default_pos, TableChange.ColumnPosition)
+        self.assertEqual(after.get_column(), "column")
+        self.assertEqual(str(first), "FIRST")
+        self.assertEqual(str(after), "AFTER column")
+        self.assertEqual(str(default_pos), "DEFAULT")
+
+    def test_column_position_equal_and_hash(self):
+        first = TableChange.ColumnPosition.first()
+        after = TableChange.ColumnPosition.after("column")
+        default_pos = TableChange.ColumnPosition.default_pos()
+
+        self.assertFalse(first == after)
+        self.assertTrue(first == TableChange.ColumnPosition.first())
+        self.assertFalse(after == default_pos)
+        self.assertTrue(after == TableChange.ColumnPosition.after("column"))
+        self.assertTrue(hash(after) == 
hash(TableChange.ColumnPosition.after("column")))
+        self.assertFalse(
+            hash(after) == hash(TableChange.ColumnPosition.after("aonther 
column"))
+        )
+        self.assertFalse(default_pos == first)
+        self.assertTrue(default_pos == 
TableChange.ColumnPosition.default_pos())
+
+    def test_add_column(self):
+        add_col_mandatory = TableChange.add_column(["col1"], 
Types.StringType.get())
+        self.assertListEqual(add_col_mandatory.get_field_name(), ["col1"])
+        self.assertListEqual(add_col_mandatory.field_name(), ["col1"])
+        self.assertEqual(add_col_mandatory.get_data_type(), 
Types.StringType.get())
+        self.assertIsNone(add_col_mandatory.get_comment())
+        self.assertIsNone(add_col_mandatory.get_position())
+        self.assertTrue(add_col_mandatory.is_nullable())
+        self.assertFalse(add_col_mandatory.is_auto_increment())
+        self.assertEqual(
+            add_col_mandatory.get_default_value(), Column.DEFAULT_VALUE_NOT_SET
+        )
+
+    def test_add_column_with_position(self):
+        field_name = ["Full Name", "First Name"]
+        data_type = Types.StringType.get()
+        comment = "First or given name"
+        position = TableChange.ColumnPosition.after("Address")
+        add_column = TableChange.add_column(field_name, data_type, comment, 
position)
+        self.assertListEqual(add_column.get_field_name(), field_name)
+        self.assertEqual(add_column.get_data_type(), data_type)
+        self.assertEqual(add_column.get_comment(), comment)
+        self.assertEqual(add_column.get_position(), position)
+        self.assertTrue(add_column.is_nullable())
+        self.assertFalse(add_column.is_auto_increment())
+        self.assertEqual(add_column.get_default_value(), 
Column.DEFAULT_VALUE_NOT_SET)
+
+    def test_add_column_with_null_comment_and_position(self):
+        field_name = ["Middle Name"]
+        data_type = Types.StringType.get()
+        add_column = TableChange.add_column(field_name, data_type, None, None)
+        self.assertListEqual(add_column.get_field_name(), field_name)
+        self.assertEqual(add_column.get_data_type(), data_type)
+        self.assertIsNone(add_column.get_comment())
+        self.assertIsNone(add_column.get_position())
+        self.assertTrue(add_column.is_nullable())
+        self.assertFalse(add_column.is_auto_increment())
+        self.assertEqual(add_column.get_default_value(), 
Column.DEFAULT_VALUE_NOT_SET)
+
+    def test_add_column_equal_and_hash(self):
+        field_name = ["Name"]
+        another_field_name = ["First Name"]
+        data_type = Types.StringType.get()
+        comment = "Person name"
+        add_columns = [
+            TableChange.add_column(field_name, data_type, comment) for _ in 
range(2)
+        ]
+        add_column_dict = {add_columns[i]: i for i in range(2)}
+
+        self.assertTrue(add_columns[0] == add_columns[1])
+        self.assertTrue(add_columns[1] == add_columns[0])
+        self.assertFalse(add_columns[0] == "invalid_add_column")
+        self.assertEqual(len(add_column_dict), 1)
+        self.assertEqual(add_column_dict[add_columns[0]], 1)
+
+        another_add_column = TableChange.add_column(
+            another_field_name, data_type, comment
+        )
+        add_column_dict = {add_columns[0]: 0, another_add_column: 1}
+        self.assertFalse(add_columns[0] == another_add_column)
+        self.assertFalse(another_add_column == add_columns[0])
+        self.assertEqual(len(add_column_dict), 2)
+
+    def test_rename_column(self):
+        field_name = ["Last Name"]
+        new_name = "Family Name"
+        rename_column = TableChange.rename_column(field_name, new_name)
+        self.assertListEqual(rename_column.get_field_name(), field_name)
+        self.assertListEqual(rename_column.field_name(), field_name)
+        self.assertEqual(rename_column.get_new_name(), new_name)
+
+    def test_rename_nested_column(self):
+        field_name = ["Name", "First Name"]
+        new_name = "Name.first"
+        rename_column = TableChange.rename_column(field_name, new_name)
+        self.assertListEqual(rename_column.field_name(), field_name)
+        self.assertEqual(rename_column.get_new_name(), new_name)
+
+    def test_column_rename_equal_and_hash(self):
+        field_name = ["Name"]
+        another_field_name = ["First Name"]
+        new_name = "Family Name"
+        rename_columns = [
+            TableChange.rename_column(field_name, new_name) for _ in range(2)
+        ]
+        rename_column_dict = {rename_columns[i]: i for i in range(2)}
+
+        self.assertTrue(rename_columns[0] == rename_columns[1])
+        self.assertTrue(rename_columns[1] == rename_columns[0])
+        self.assertFalse(rename_columns[0] == "invalid_rename_column")
+        self.assertEqual(len(rename_column_dict), 1)
+        self.assertEqual(rename_column_dict[rename_columns[0]], 1)
+
+        another_rename_column = TableChange.rename_column(another_field_name, 
new_name)
+        rename_column_dict = {rename_columns[0]: 0, another_rename_column: 1}
+        self.assertFalse(rename_columns[0] == another_rename_column)
+        self.assertFalse(another_rename_column == rename_columns[0])
+        self.assertEqual(len(rename_column_dict), 2)
+
+    def test_update_column_default_value(self):
+        field_name_data = [["existing_column"], ["nested", "existing_column"]]
+        new_default_value = Literals.of("Default Value", 
Types.VarCharType.of(255))
+
+        for field_name in field_name_data:
+            update_column_default_value = 
TableChange.update_column_default_value(
+                field_name, new_default_value
+            )
+            self.assertListEqual(update_column_default_value.field_name(), 
field_name)
+            self.assertEqual(
+                update_column_default_value.get_new_default_value(), 
new_default_value
+            )
+
+        update_column_default_value = TableChange.update_column_default_value(
+            field_name_data[0], Column.DEFAULT_VALUE_NOT_SET
+        )
+        self.assertListEqual(
+            update_column_default_value.field_name(), field_name_data[0]
+        )
+        self.assertEqual(
+            update_column_default_value.get_new_default_value(),
+            Column.DEFAULT_VALUE_NOT_SET,
+        )
+
+    def test_update_column_default_value_equal_and_hash(self):
+        field_name = ["existing_column"]
+        new_default_value = Literals.of("Default Value", 
Types.VarCharType.of(255))
+        update_column_default_values = [
+            TableChange.update_column_default_value(field_name, 
new_default_value)
+            for _ in range(2)
+        ]
+        update_column_default_value_dict = {
+            update_column_default_values[i]: i for i in range(2)
+        }
+
+        self.assertTrue(
+            update_column_default_values[0] == update_column_default_values[1]
+        )
+        self.assertTrue(
+            update_column_default_values[1] == update_column_default_values[0]
+        )
+        self.assertFalse(
+            update_column_default_values[0] == 
"invalid_update_column_default_value"
+        )
+        self.assertEqual(len(update_column_default_value_dict), 1)
+        self.assertEqual(
+            update_column_default_value_dict[update_column_default_values[0]], 
1
+        )
+
+        update_column_default_values = [
+            TableChange.update_column_default_value(
+                field_name, Column.DEFAULT_VALUE_NOT_SET
+            )
+            for _ in range(2)
+        ]
+
+        update_column_default_value_dict = {
+            update_column_default_values[i]: i for i in range(2)
+        }
+
+        self.assertTrue(
+            update_column_default_values[0] == update_column_default_values[1]
+        )
+        self.assertTrue(
+            update_column_default_values[1] == update_column_default_values[0]
+        )
+        self.assertEqual(len(update_column_default_value_dict), 1)
+        self.assertEqual(
+            update_column_default_value_dict[update_column_default_values[0]], 
1
+        )
+
+    def test_update_column_type(self):
+        field_name_data = [["existing_column"], ["nested", "existing_column"]]
+        data_type = Types.StringType.get()
+        for field_name in field_name_data:
+            update_column_type = TableChange.update_column_type(field_name, 
data_type)
+            self.assertListEqual(update_column_type.field_name(), field_name)
+            self.assertListEqual(update_column_type.get_field_name(), 
field_name)
+            self.assertTrue(update_column_type.get_new_data_type() == 
data_type)
+
+    def test_update_column_type_equal_and_hash(self):
+        field_name = ["First Name"]
+        data_type = Types.StringType.get()
+        update_column_types = [
+            TableChange.update_column_type(field_name, data_type) for _ in 
range(2)
+        ]
+        update_column_type_dict = {update_column_types[i]: i for i in range(2)}
+
+        self.assertTrue(update_column_types[0] == update_column_types[1])
+        self.assertTrue(update_column_types[1] == update_column_types[0])
+        self.assertFalse(update_column_types[0] == 
"invalid_update_column_type")
+        self.assertEqual(len(update_column_type_dict), 1)
+        self.assertEqual(update_column_type_dict[update_column_types[0]], 1)
+
+        another_update_column_type = TableChange.update_column_type(
+            ["Last Name"], data_type
+        )
+        update_column_type_dict = {
+            update_column_types[0]: 0,
+            another_update_column_type: 1,
+        }
+        self.assertFalse(update_column_types[0] == another_update_column_type)
+        self.assertFalse(another_update_column_type == update_column_types[0])
+        self.assertEqual(len(update_column_type_dict), 2)
+
+    def test_update_column_comment(self):
+        field_name_data = [["First Name"], ["nested", "Last Name"]]
+        comment_data = ["First or given name", "Last or family name"]
+        for field_name, comment in zip(field_name_data, comment_data):
+            update_column_comment = TableChange.update_column_comment(
+                field_name, comment
+            )
+            self.assertListEqual(update_column_comment.field_name(), 
field_name)
+            self.assertListEqual(update_column_comment.get_field_name(), 
field_name)
+            self.assertEqual(update_column_comment.get_new_comment(), comment)
+
+    def test_update_column_comment_equal_and_hash(self):
+        field_name = ["First Name"]
+        comment = "First or given name"
+        update_column_comments = [
+            TableChange.update_column_comment(field_name, comment) for _ in 
range(2)
+        ]
+        update_column_comment_dict = {update_column_comments[i]: i for i in 
range(2)}
+        self.assertTrue(update_column_comments[0] == update_column_comments[1])
+        self.assertTrue(update_column_comments[1] == update_column_comments[0])
+        self.assertEqual(len(update_column_comment_dict), 1)
+
+        another_update_column_comment = TableChange.update_column_comment(
+            ["Last Name"], "Last or family name"
+        )
+        update_column_comment_dict = {
+            update_column_comments[0]: 0,
+            another_update_column_comment: 1,
+        }
+        self.assertFalse(update_column_comments[0] == 
another_update_column_comment)
+        self.assertFalse(another_update_column_comment == 
update_column_comments[0])
+        self.assertFalse(update_column_comments[0] == 
"invalid_update_column_comment")
+        self.assertEqual(len(update_column_comment_dict), 2)
+
+    def test_update_column_position(self):
+        field_name_data = [["First Name"], ["nested", "Last Name"]]
+        position_data = [
+            TableChange.ColumnPosition.first(),
+            TableChange.ColumnPosition.after("First Name"),
+        ]
+        for field_name, position in zip(field_name_data, position_data):
+            update_column_position = TableChange.update_column_position(
+                field_name, position
+            )
+            self.assertListEqual(update_column_position.field_name(), 
field_name)
+            self.assertListEqual(update_column_position.get_field_name(), 
field_name)
+            self.assertEqual(update_column_position.get_position(), position)
+
+    def test_update_column_position_equal_and_hash(self):
+        field_name = ["First Name"]
+        position = TableChange.ColumnPosition.first()
+        update_column_positions = [
+            TableChange.update_column_position(field_name, position) for _ in 
range(2)
+        ]
+        update_column_position_dict = {update_column_positions[i]: i for i in 
range(2)}
+        self.assertTrue(update_column_positions[0] == 
update_column_positions[1])
+        self.assertTrue(update_column_positions[1] == 
update_column_positions[0])
+        self.assertEqual(len(update_column_position_dict), 1)
+
+        another_update_column_position = TableChange.update_column_position(
+            ["Last Name"], TableChange.ColumnPosition.after("First Name")
+        )
+        update_column_position_dict = {
+            update_column_positions[0]: 0,
+            another_update_column_position: 1,
+        }
+        self.assertFalse(update_column_positions[0] == 
another_update_column_position)
+        self.assertFalse(another_update_column_position == 
update_column_positions[0])
+        self.assertFalse(update_column_positions[0] == 
"invalid_update_column_position")
+        self.assertEqual(len(update_column_position_dict), 2)
+
+    def test_delete_column(self):
+        field_name_data = [["existing_column"], ["nested", "existing_column"]]
+        if_exists_data = [True, False]
+        for field_name, if_exists in zip(field_name_data, if_exists_data):
+            delete_column = TableChange.delete_column(field_name, if_exists)
+            self.assertListEqual(delete_column.field_name(), field_name)
+            self.assertListEqual(delete_column.get_field_name(), field_name)
+            self.assertEqual(delete_column.get_if_exists(), if_exists)
+
+    def test_delete_column_equal_and_hash(self):
+        field_name = ["Column A"]
+        if_exists = True
+        delete_columns = [
+            TableChange.delete_column(field_name, if_exists) for _ in range(2)
+        ]
+        delete_column_dict = {delete_columns[i]: i for i in range(2)}
+        self.assertTrue(delete_columns[0] == delete_columns[1])
+        self.assertTrue(delete_columns[1] == delete_columns[0])
+        self.assertEqual(len(delete_column_dict), 1)
+
+        another_delete_column = TableChange.delete_column(["Column B"], 
if_exists)
+        delete_column_dict = {delete_columns[0]: 0, another_delete_column: 1}
+        self.assertFalse(delete_columns[0] == another_delete_column)
+        self.assertFalse(another_delete_column == delete_columns[0])
+        self.assertFalse(delete_columns[0] == "invalid_delete_column")
+        self.assertEqual(len(delete_column_dict), 2)
+
+    def test_update_column_nullability(self):
+        field_name_data = [["existing_column"], ["nested", "existing_column"]]
+        nullable_data = [True, False]
+        for field_name, nullable in zip(field_name_data, nullable_data):
+            update_column_nullability = TableChange.update_column_nullability(
+                field_name, nullable
+            )
+            self.assertListEqual(update_column_nullability.field_name(), 
field_name)
+            self.assertListEqual(update_column_nullability.get_field_name(), 
field_name)
+            self.assertEqual(update_column_nullability.get_nullable(), 
nullable)
+
+    def test_update_column_nullability_equal_and_hash(self):
+        field_name = ["Column A"]
+        nullable = True
+        update_column_nullabilities = [
+            TableChange.update_column_nullability(field_name, nullable)
+            for _ in range(2)
+        ]
+        update_column_nullability_dict = {
+            update_column_nullabilities[i]: i for i in range(2)
+        }
+        self.assertTrue(
+            update_column_nullabilities[0] == update_column_nullabilities[1]
+        )
+        self.assertTrue(
+            update_column_nullabilities[1] == update_column_nullabilities[0]
+        )
+        self.assertEqual(len(update_column_nullability_dict), 1)
+
+        another_update_column_nullability = 
TableChange.update_column_nullability(
+            ["Column B"], False
+        )
+        update_column_nullability_dict = {
+            update_column_nullabilities[0]: 0,
+            another_update_column_nullability: 1,
+        }
+        self.assertFalse(
+            update_column_nullabilities[0] == another_update_column_nullability
+        )
+        self.assertFalse(
+            another_update_column_nullability == update_column_nullabilities[0]
+        )
+        self.assertFalse(
+            update_column_nullabilities[0] == 
"invalid_update_column_nullability"
+        )
+        self.assertEqual(len(update_column_nullability_dict), 2)
+
+    def test_add_index(self):
+        index_name = "index_name"
+        field_names = [["id"]]
+        index_type = Index.IndexType.PRIMARY_KEY
+        add_index = TableChange.add_index(index_type, index_name, field_names)
+        self.assertEqual(add_index.get_name(), index_name)
+        self.assertListEqual(add_index.get_field_names(), field_names)
+        self.assertIs(add_index.get_type(), index_type)
+
+    def test_add_index_equal_and_hash(self):
+        index_name = "index_name"
+        field_names = [["Column A"], ["Column B"]]
+        index_type = Index.IndexType.UNIQUE_KEY
+        add_indexes = [
+            TableChange.add_index(index_type, index_name, field_names) for _ 
in range(2)
+        ]
+        add_index_dict = {add_indexes[i]: i for i in range(2)}
+        self.assertTrue(add_indexes[0] == add_indexes[1])
+        self.assertTrue(add_indexes[1] == add_indexes[0])
+        self.assertFalse(add_indexes[0] == "invalid_add_index")
+        self.assertEqual(len(add_index_dict), 1)
+
+        another_add_index = TableChange.add_index(
+            Index.IndexType.PRIMARY_KEY, "another_index_name", [["id"]]
+        )
+        add_index_dict = {add_indexes[0]: 0, another_add_index: 1}
+        self.assertFalse(add_indexes[0] == another_add_index)
+        self.assertFalse(another_add_index == add_indexes[0])
+        self.assertEqual(len(add_index_dict), 2)
+
+    def test_delete_index(self):
+        index_name = "index_name"
+        if_exists = True
+        delete_index = TableChange.delete_index(index_name, if_exists)
+        self.assertEqual(delete_index.get_name(), index_name)
+        self.assertEqual(delete_index.is_if_exists(), if_exists)
+
+    def test_delete_index_equal_and_hash(self):
+        index_name = "index_name"
+        if_exists = True
+        delete_indexes = [
+            TableChange.delete_index(index_name, if_exists) for _ in range(2)
+        ]
+        delete_index_dict = {delete_indexes[i]: i for i in range(2)}
+        self.assertTrue(delete_indexes[0] == delete_indexes[1])
+        self.assertTrue(delete_indexes[1] == delete_indexes[0])
+        self.assertFalse(delete_indexes[0] == "invalid_delete_index")
+        self.assertEqual(len(delete_index_dict), 1)
+
+        another_delete_index = TableChange.delete_index("another_index_name", 
if_exists)
+        delete_index_dict = {delete_indexes[0]: 0, another_delete_index: 1}
+        self.assertFalse(delete_indexes[0] == another_delete_index)
+        self.assertFalse(another_delete_index == delete_indexes[0])
+        self.assertEqual(len(delete_index_dict), 2)
+
+    def test_update_column_auto_increment(self):
+        field_name_data = [["existing_column"], ["nested", "existing_column"]]
+        auto_increment_data = [True, False]
+        for field_name, auto_increment in zip(field_name_data, 
auto_increment_data):
+            update_column_auto_increment = 
TableChange.update_column_auto_increment(
+                field_name, auto_increment
+            )
+            self.assertListEqual(update_column_auto_increment.field_name(), 
field_name)
+            self.assertEqual(
+                update_column_auto_increment.is_auto_increment(), 
auto_increment
+            )
+
+    def test_update_column_auto_increment_equal_and_hash(self):
+        field_name = ["Column A"]
+        auto_increment = True
+        update_column_auto_increments = [
+            TableChange.update_column_auto_increment(field_name, 
auto_increment)
+            for _ in range(2)
+        ]
+        update_column_auto_increment_dict = {
+            update_column_auto_increments[i]: i for i in range(2)
+        }
+        self.assertTrue(
+            update_column_auto_increments[0] == 
update_column_auto_increments[1]
+        )
+        self.assertTrue(
+            update_column_auto_increments[1] == 
update_column_auto_increments[0]
+        )
+        self.assertEqual(len(update_column_auto_increment_dict), 1)
+
+        another_update_column_auto_increment = 
TableChange.update_column_auto_increment(
+            ["Column B"], False
+        )
+        update_column_auto_increment_dict = {
+            update_column_auto_increments[0]: 0,
+            another_update_column_auto_increment: 1,
+        }
+        self.assertFalse(
+            update_column_auto_increments[0] == 
another_update_column_auto_increment
+        )
+        self.assertFalse(
+            another_update_column_auto_increment == 
update_column_auto_increments[0]
+        )
+        self.assertFalse(
+            update_column_auto_increments[0] == 
"invalid_update_column_auto_increment"
+        )
+        self.assertEqual(len(update_column_auto_increment_dict), 2)
diff --git a/clients/client-python/tests/unittests/rel/test_transforms.py 
b/clients/client-python/tests/unittests/rel/test_transforms.py
index 495c88775b..0b354b7a8d 100644
--- a/clients/client-python/tests/unittests/rel/test_transforms.py
+++ b/clients/client-python/tests/unittests/rel/test_transforms.py
@@ -21,8 +21,8 @@ from itertools import combinations
 
 from gravitino.api.expressions.literals.literals import Literals
 from gravitino.api.expressions.named_reference import NamedReference
-from gravitino.api.expressions.partitions.partitions import Partitions
 from gravitino.api.expressions.transforms.transforms import Transforms
+from gravitino.api.rel.partitions.partitions import Partitions
 
 
 class TestTransforms(unittest.TestCase):
diff --git a/clients/client-python/tests/unittests/rel/test_types.py 
b/clients/client-python/tests/unittests/rel/test_types.py
index c742281e7e..940b11f99d 100644
--- a/clients/client-python/tests/unittests/rel/test_types.py
+++ b/clients/client-python/tests/unittests/rel/test_types.py
@@ -17,7 +17,7 @@
 
 import unittest
 
-from gravitino.api.types.types import Types, Name
+from gravitino.api.rel.types.types import Name, Types
 
 
 class TestTypes(unittest.TestCase):
diff --git a/clients/client-python/tests/unittests/test_column.py 
b/clients/client-python/tests/unittests/test_column.py
index 672ce5d82d..d3ca33c73f 100644
--- a/clients/client-python/tests/unittests/test_column.py
+++ b/clients/client-python/tests/unittests/test_column.py
@@ -18,10 +18,10 @@
 import unittest
 from unittest.mock import Mock
 
-from gravitino.api.column import Column, ColumnImpl
 from gravitino.api.expressions.expression import Expression
 from gravitino.api.expressions.function_expression import FunctionExpression
-from gravitino.api.types.type import Type
+from gravitino.api.rel.column import Column, ColumnImpl
+from gravitino.api.rel.types.type import Type
 from gravitino.exceptions.base import (
     IllegalArgumentException,
     UnsupportedOperationException,

Reply via email to