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

unknowntpo 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 417e146eaa [#8806] feat(client-python): add from_function_arg(s) to 
DTOConverters (#8819)
417e146eaa is described below

commit 417e146eaa1c5e65f909d73a81b83e7681dba27f
Author: George T. C. Lai <[email protected]>
AuthorDate: Thu Oct 16 21:57:51 2025 +0800

    [#8806] feat(client-python): add from_function_arg(s) to DTOConverters 
(#8819)
    
    <!--
    1. Title: [#<issue>] <type>(<scope>): <subject>
       Examples:
         - "[#123] feat(operator): support xxx"
         - "[#233] fix: check null before access result in xxx"
         - "[MINOR] refactor: fix typo in variable name"
         - "[MINOR] docs: fix typo in README"
         - "[#255] test: fix flaky test NameOfTheTest"
       Reference: https://www.conventionalcommits.org/en/v1.0.0/
    2. If the PR is unfinished, please mark this PR as draft.
    -->
    
    ### What changes were proposed in this pull request?
    
    This PR is aimed at implementing the following two methods in class
    `gravitino.dto.util.dto_converters.DTOConverters`
    
    - `from_function_arg`
    - `from_function_args`
    
    We can therefore implement the method `from_dto` in the same class.
    
    ### Why are the changes needed?
    
    It's required for implementing table operations.
    
    Fix: #8806
    
    ### 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]>
---
 .../client-python/gravitino/dto/util/__init__.py   |  16 +++
 .../gravitino/dto/util/dto_converters.py           |  78 +++++++++++
 .../tests/unittests/dto/util/__init__.py           |  16 +++
 .../unittests/dto/util/test_dto_converters.py      | 148 +++++++++++++++++++++
 4 files changed, 258 insertions(+)

diff --git a/clients/client-python/gravitino/dto/util/__init__.py 
b/clients/client-python/gravitino/dto/util/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/clients/client-python/gravitino/dto/util/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/clients/client-python/gravitino/dto/util/dto_converters.py 
b/clients/client-python/gravitino/dto/util/dto_converters.py
new file mode 100644
index 0000000000..5394a01677
--- /dev/null
+++ b/clients/client-python/gravitino/dto/util/dto_converters.py
@@ -0,0 +1,78 @@
+# 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 typing import cast
+
+from gravitino.api.rel.expressions.expression import Expression
+from gravitino.api.rel.expressions.function_expression import 
FunctionExpression
+from gravitino.api.rel.expressions.literals.literals import Literals
+from gravitino.api.rel.expressions.named_reference import NamedReference
+from gravitino.api.rel.expressions.unparsed_expression import 
UnparsedExpression
+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
+from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
+from gravitino.dto.rel.expressions.unparsed_expression_dto import 
UnparsedExpressionDTO
+from gravitino.exceptions.base import IllegalArgumentException
+
+
+class DTOConverters:
+    """Utility class for converting between DTOs and domain objects."""
+
+    @staticmethod
+    def from_function_arg(arg: FunctionArg) -> Expression:
+        """Converts a FunctionArg DTO to an Expression.
+
+        Args:
+            arg (FunctionArg): The function argument DTO to be converted.
+
+        Returns:
+            Expression: The expression.
+        """
+        arg_type = arg.arg_type()
+        if arg_type is FunctionArg.ArgType.LITERAL:
+            dto = cast(LiteralDTO, arg)
+            if dto.value() is None or dto.data_type() == Types.NullType.get():
+                return Literals.NULL
+            return Literals.of(dto.value(), dto.data_type())
+        if arg_type is FunctionArg.ArgType.FIELD:
+            dto = cast(FieldReferenceDTO, arg)
+            return NamedReference.field(dto.field_name())
+        if arg_type is FunctionArg.ArgType.FUNCTION:
+            dto = cast(FuncExpressionDTO, arg)
+            return FunctionExpression.of(
+                dto.function_name(), 
*DTOConverters.from_function_args(dto.args())
+            )
+        if arg_type is FunctionArg.ArgType.UNPARSED:
+            dto = cast(UnparsedExpressionDTO, arg)
+            return UnparsedExpression.of(dto.unparsed_expression())
+        raise IllegalArgumentException(f"Unsupported expression type: {arg}")
+
+    @staticmethod
+    def from_function_args(args: list[FunctionArg]) -> list[Expression]:
+        """Converts a FunctionArg DTO to an Expression.
+
+        Args:
+            args (list[FunctionArg]): The function argument DTOs to be 
converted.
+
+        Returns:
+            list[Expression]: The list of expressions.
+        """
+        if not args:
+            return Expression.EMPTY_EXPRESSION
+        return [DTOConverters.from_function_arg(arg) for arg in args]
diff --git a/clients/client-python/tests/unittests/dto/util/__init__.py 
b/clients/client-python/tests/unittests/dto/util/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/clients/client-python/tests/unittests/dto/util/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git 
a/clients/client-python/tests/unittests/dto/util/test_dto_converters.py 
b/clients/client-python/tests/unittests/dto/util/test_dto_converters.py
new file mode 100644
index 0000000000..a7c685c70b
--- /dev/null
+++ b/clients/client-python/tests/unittests/dto/util/test_dto_converters.py
@@ -0,0 +1,148 @@
+# 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 datetime import datetime
+from random import randint, random
+from typing import cast
+from unittest.mock import MagicMock, patch
+
+from gravitino.api.rel.expressions.expression import Expression
+from gravitino.api.rel.expressions.function_expression import 
FunctionExpression
+from gravitino.api.rel.expressions.literals.literals import Literals
+from gravitino.api.rel.expressions.named_reference import FieldReference
+from gravitino.api.rel.expressions.unparsed_expression import 
UnparsedExpression
+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
+from gravitino.dto.rel.expressions.literal_dto import LiteralDTO
+from gravitino.dto.rel.expressions.unparsed_expression_dto import 
UnparsedExpressionDTO
+from gravitino.dto.util.dto_converters import DTOConverters
+from gravitino.exceptions.base import IllegalArgumentException
+
+
+class TestDTOConverters(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls) -> None:
+        random_integer = randint(-100, 100)
+        random_float = round(random() * 100, 2)
+        cls.literals = {
+            Types.NullType.get(): None,
+            Types.BooleanType.get(): True,
+            Types.IntegerType.get(): random_integer,
+            Types.DoubleType.get(): random_float,
+            Types.StringType.get(): "test",
+            Types.FloatType.get(): random_float,
+            Types.ShortType.get(): random_integer,
+            Types.LongType.get(): random_integer,
+            Types.DecimalType.of(10, 2): random_float,
+            Types.DateType.get(): "2025-10-14",
+            Types.TimestampType.without_time_zone(): datetime.fromisoformat(
+                "2025-10-14T00:00:00.000"
+            ),
+            Types.TimeType.get(): "11:34:58.123",
+            Types.BinaryType.get(): bytes("test", "utf-8"),
+            Types.VarCharType.of(10): "test",
+            Types.FixedCharType.of(10): "test",
+        }
+
+    def test_from_function_arg_literal_dto(self):
+        for data_type, value in TestDTOConverters.literals.items():
+            expected = Literals.of(value=value, data_type=data_type)
+            literal_dto = (
+                
LiteralDTO.builder().with_data_type(data_type).with_value(value).build()
+            )
+            self.assertTrue(DTOConverters.from_function_arg(literal_dto) == 
expected)
+
+    def test_from_function_arg_func_expression_dto(self):
+        function_name = "test_function"
+        args: list[FunctionArg] = [
+            LiteralDTO.builder()
+            .with_data_type(Types.IntegerType.get())
+            .with_value("-1")
+            .build(),
+            LiteralDTO.builder()
+            .with_data_type(Types.BooleanType.get())
+            .with_value("True")
+            .build(),
+        ]
+        func_expression_dto = (
+            FuncExpressionDTO.builder()
+            .with_function_name(function_name)
+            .with_function_args(args)
+            .build()
+        )
+        expected = FunctionExpression.of(
+            function_name,
+            Literals.of(value="-1", data_type=Types.IntegerType.get()),
+            Literals.of(value="True", data_type=Types.BooleanType.get()),
+        )
+        converted = DTOConverters.from_function_arg(func_expression_dto)
+        self.assertIsInstance(converted, FunctionExpression)
+        converted = cast(FunctionExpression, converted)
+        self.assertEqual(converted.function_name(), expected.function_name())
+        self.assertListEqual(converted.arguments(), expected.arguments())
+
+    def test_from_function_arg_field_reference(self):
+        field_names = [f"field_{i}" for i in range(3)]
+        field_reference_dto = (
+            
FieldReferenceDTO.builder().with_field_name(field_name=field_names).build()
+        )
+        expected = FieldReference(field_names=field_names)
+        converted = DTOConverters.from_function_arg(field_reference_dto)
+        self.assertIsInstance(converted, FieldReference)
+        self.assertTrue(converted == expected)
+
+    def test_from_function_arg_unparsed(self):
+        expected = UnparsedExpression.of(unparsed_expression="unparsed")
+        unparsed_expression_dto = (
+            
UnparsedExpressionDTO.builder().with_unparsed_expression("unparsed").build()
+        )
+        converted = DTOConverters.from_function_arg(unparsed_expression_dto)
+        self.assertIsInstance(converted, UnparsedExpression)
+        self.assertTrue(converted == expected)
+
+    @patch.object(FunctionArg, "arg_type", return_value="invalid_type")
+    def test_from_function_arg_raises_exception(
+        self, mock_function_arg_arg_type: MagicMock
+    ):
+        with self.assertRaisesRegex(
+            IllegalArgumentException, "Unsupported expression type"
+        ):
+            DTOConverters.from_function_arg(mock_function_arg_arg_type)
+
+    def test_from_function_args(self):
+        args = [
+            LiteralDTO.builder()
+            .with_data_type(Types.IntegerType.get())
+            .with_value("-1")
+            .build(),
+            LiteralDTO.builder()
+            .with_data_type(Types.BooleanType.get())
+            .with_value("True")
+            .build(),
+        ]
+        expected = [
+            Literals.of(value="-1", data_type=Types.IntegerType.get()),
+            Literals.of(value="True", data_type=Types.BooleanType.get()),
+        ]
+        converted = DTOConverters.from_function_args(args)
+        self.assertListEqual(converted, expected)
+        self.assertListEqual(
+            DTOConverters.from_function_args([]), Expression.EMPTY_EXPRESSION
+        )

Reply via email to